├── .travis.yml
├── src
├── .eslintrc
├── index.test.js
├── components
│ ├── ButtonsList.js
│ ├── Button.js
│ └── Toggler.js
├── index.js
└── index.css
├── .eslintignore
├── screenshots
├── 270.gif
├── 360.gif
├── up.gif
├── left.gif
├── right.gif
├── down-180.gif
└── down-90.gif
├── example
├── public
│ ├── logo.png
│ ├── manifest.json
│ ├── index.html
│ └── style.css
├── src
│ ├── index.js
│ ├── App.test.js
│ ├── index.css
│ ├── assets
│ │ ├── linkedin.svg
│ │ ├── medium.svg
│ │ ├── github.svg
│ │ ├── spotify.svg
│ │ ├── dribbble.svg
│ │ ├── instagram.svg
│ │ ├── twitter.svg
│ │ └── share.svg
│ └── App.js
├── README.md
└── package.json
├── .npmignore
├── .editorconfig
├── .prettierrc
├── rollup.config.js
├── .eslintrc
├── .gitignore
├── LICENSE
├── package.json
└── README.md
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 12
4 | - 10
5 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | node_modules/
4 | .snapshots/
5 | *.min.js
--------------------------------------------------------------------------------
/screenshots/270.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/270.gif
--------------------------------------------------------------------------------
/screenshots/360.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/360.gif
--------------------------------------------------------------------------------
/screenshots/up.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/up.gif
--------------------------------------------------------------------------------
/screenshots/left.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/left.gif
--------------------------------------------------------------------------------
/screenshots/right.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/right.gif
--------------------------------------------------------------------------------
/example/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/example/public/logo.png
--------------------------------------------------------------------------------
/screenshots/down-180.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/down-180.gif
--------------------------------------------------------------------------------
/screenshots/down-90.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AM-77/react-floating-buttons/HEAD/screenshots/down-90.gif
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist
2 | .babelrc
3 | .storybook
4 | .gitignore
5 | .prettierrc
6 | rollup.config.js
7 | tsconfig.json
8 |
--------------------------------------------------------------------------------
/src/index.test.js:
--------------------------------------------------------------------------------
1 | import FloatingButtons from '.'
2 |
3 | describe('FloatingButtons', () => {
4 | it('is truthy', () => {
5 | expect(FloatingButtons).toBeTruthy()
6 | })
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import './index.css'
2 |
3 | import React from 'react'
4 | import ReactDOM from 'react-dom'
5 | import App from './App'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "jsxSingleQuote": true,
4 | "semi": false,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "jsxBracketSameLine": false,
8 | "arrowParens": "always",
9 | "trailingComma": "none"
10 | }
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | It is linked to the react-nav-button package in the parent directory for development purposes.
4 |
5 | You can run `yarn install` and then `yarn start` to test your package.
6 |
--------------------------------------------------------------------------------
/example/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render(, div)
8 | ReactDOM.unmountComponentAtNode(div)
9 | })
10 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-nav-button",
3 | "name": "react-nav-button",
4 | "icons": [
5 | {
6 | "src": "logo.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import sass from 'rollup-plugin-sass'
2 | import typescript from 'rollup-plugin-typescript2'
3 |
4 | import pkg from './package.json'
5 |
6 | export default {
7 | input: 'src/index.tsx',
8 | output: [
9 | {
10 | file: pkg.main,
11 | format: 'cjs',
12 | exports: 'named',
13 | sourcemap: true,
14 | strict: false
15 | }
16 | ],
17 | plugins: [sass({ insert: true }), typescript()],
18 | external: ['react', 'react-dom']
19 | }
20 |
--------------------------------------------------------------------------------
/example/src/assets/linkedin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 | react-floating-buttons exemples
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:prettier/recommended",
7 | "prettier/standard",
8 | "prettier/react"
9 | ],
10 | "env": {
11 | "node": true
12 | },
13 | "parserOptions": {
14 | "ecmaVersion": 2020,
15 | "ecmaFeatures": {
16 | "legacyDecorators": true,
17 | "jsx": true
18 | }
19 | },
20 | "settings": {
21 | "react": {
22 | "version": "16"
23 | }
24 | },
25 | "rules": {
26 | "space-before-function-paren": 0,
27 | "react/prop-types": 0,
28 | "react/jsx-handler-names": 0,
29 | "react/jsx-fragments": 0,
30 | "react/no-unused-prop-types": 0,
31 | "import/export": 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/src/assets/medium.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Directory for instrumented libs generated by jscoverage/JSCover
10 | lib-cov
11 |
12 | # Coverage directory used by tools like istanbul
13 | coverage
14 | *.lcov
15 |
16 | # nyc test coverage
17 | .nyc_output
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (https://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directories
26 | node_modules/
27 | jspm_packages/
28 |
29 | # TypeScript v1 declaration files
30 | typings/
31 |
32 | # TypeScript cache
33 | *.tsbuildinfo
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional eslint cache
39 | .eslintcache
40 |
41 | # Output of 'npm pack'
42 | *.tgz
43 |
44 | # Yarn Integrity file
45 | .yarn-integrity
46 |
47 | # generate output
48 | dist
49 |
--------------------------------------------------------------------------------
/example/src/assets/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-floating-buttons-example",
3 | "homepage": "https://AM-77.github.io/react-floating-buttons",
4 | "version": "1.0.0",
5 | "private": true,
6 | "dependencies": {
7 | "react": "link:../node_modules/react",
8 | "react-dom": "link:../node_modules/react-dom",
9 | "react-scripts": "link:../node_modules/react-scripts",
10 | "react-floating-buttons": "link:.."
11 | },
12 | "scripts": {
13 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
14 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
15 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
16 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
17 | },
18 | "eslintConfig": {
19 | "extends": "react-app"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/example/src/assets/spotify.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/src/assets/dribbble.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Vincent Mancini
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 |
--------------------------------------------------------------------------------
/example/public/style.css:
--------------------------------------------------------------------------------
1 | *{
2 | padding: 0;
3 | margin: 0;
4 | box-sizing: border-box;
5 | }
6 |
7 | #root{
8 | padding: 50px 0;
9 | overflow: hidden;
10 | background-color: #fdfdfd
11 | }
12 |
13 | h1, h2{
14 | text-align: center;
15 | color: #2d2d2d;
16 | font-weight: 300;
17 | text-decoration: underline
18 | }
19 |
20 | .wrapper{
21 | display: block;
22 | padding: 5%;
23 | width: 100%
24 | }
25 |
26 | .exemple-wrapper .exemples{
27 | margin: 30px 0;
28 | }
29 |
30 | .exemple-wrapper .exemples .exemple{
31 | display: block;
32 | width: 100%;
33 | position: relative
34 | }
35 |
36 | .exemple-wrapper .exemples .exemple code{
37 | display: block;
38 | text-align: center;
39 | font-size: 14px;
40 | background-color: #efefef;
41 | padding: 10px;
42 | line-height: 3;
43 | border-radius: 5px;
44 | margin: 0 auto;
45 | }
46 |
47 | .exemple-wrapper .component{
48 | position: relative;
49 | height: 150px;
50 | }
51 |
52 | .exemple-wrapper.vertical .exemples,
53 | .exemple-wrapper.circular .exemples{
54 | flex: wrap row;
55 | width: 100%;
56 | }
57 |
58 | .exemple-wrapper.vertical .component{
59 | height: 350px;
60 | }
61 |
62 | .exemple-wrapper.circular .component{
63 | height: 450px;
64 | }
65 |
66 | .exemple-wrapper.circular .component.half{
67 | height: 250px;
68 | }
69 |
70 | .red{
71 | background: red
72 | }
--------------------------------------------------------------------------------
/example/src/assets/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ButtonsList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import Button from './Button'
4 |
5 | class ButtonsList extends Component {
6 |
7 | render() {
8 | const { dimension, buttonsList, itemBackgroundColor, direction, degree, distance, isOpen} = this.props
9 | return (
10 | {
11 | buttonsList.map((item, index) => )
24 | }
25 | )
26 | }
27 | }
28 |
29 | ButtonsList.defaultProps = {
30 | buttonsList: []
31 | }
32 |
33 | ButtonsList.propTypes = {
34 | buttonsList: PropTypes.array,
35 | distance: PropTypes.number,
36 | dimension: PropTypes.number.isRequired,
37 | itemBackgroundColor: PropTypes.string,
38 | isOpen: PropTypes.bool.isRequired,
39 | direction: PropTypes.string.isRequired,
40 | degree: PropTypes.number.isRequired,
41 | }
42 |
43 | export default ButtonsList
--------------------------------------------------------------------------------
/example/src/assets/twitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-floating-buttons",
3 | "version": "1.0.1",
4 | "description": "A lightweight, customizable and cool ⚛️ react navigation buttons",
5 | "author": "AM-77",
6 | "license": "MIT",
7 | "repository": "AM-77/react-floating-buttons",
8 | "main": "dist/index.js",
9 | "module": "dist/index.modern.js",
10 | "source": "src/index.js",
11 | "engines": {
12 | "node": ">=10"
13 | },
14 | "scripts": {
15 | "build": "microbundle-crl --no-compress --format modern,cjs",
16 | "start": "microbundle-crl watch --no-compress --format modern,cjs",
17 | "prepublish": "run-s build",
18 | "test": "run-s test:unit test:lint test:build",
19 | "test:build": "run-s build",
20 | "test:lint": "eslint .",
21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
22 | "test:watch": "react-scripts test --env=jsdom",
23 | "predeploy": "cd example && yarn install && yarn run build",
24 | "deploy": "gh-pages -d example/build"
25 | },
26 | "peerDependencies": {
27 | "react": "^16.0.0"
28 | },
29 | "devDependencies": {
30 | "microbundle-crl": "^0.13.8",
31 | "babel-eslint": "^10.0.3",
32 | "cross-env": "^7.0.2",
33 | "eslint": "^6.8.0",
34 | "eslint-config-prettier": "^6.7.0",
35 | "eslint-config-standard": "^14.1.0",
36 | "eslint-config-standard-react": "^9.2.0",
37 | "eslint-plugin-import": "^2.18.2",
38 | "eslint-plugin-node": "^11.0.0",
39 | "eslint-plugin-prettier": "^3.1.1",
40 | "eslint-plugin-promise": "^4.2.1",
41 | "eslint-plugin-react": "^7.17.0",
42 | "eslint-plugin-standard": "^4.0.1",
43 | "gh-pages": "^2.2.0",
44 | "npm-run-all": "^4.1.5",
45 | "prettier": "^2.0.4",
46 | "react": "^16.13.1",
47 | "react-dom": "^16.13.1",
48 | "react-scripts": "^3.4.1"
49 | },
50 | "files": [
51 | "dist"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/example/src/assets/share.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} buttonType the main button type (hmaburger, plus, vert-dots or hori-dots)
3 | * @param {number} dimension the buttons dimension
4 | * @param {number} top the offsetTop position of the nav
5 | * @param {number} left the offsetLeft position of the nav
6 | * @param {string} backgroundColor the main button background color
7 | * @param {string} itemBackgroundColor the nav item button background color
8 | * @param {string} buttonColor the color of the main button
9 | * @param {string} direction the direction of the nav when opened (left, right, top, bottom and circular)
10 | * @param {number} distance the distance between the main button and the nav items ** required when direction='circular' **
11 | * @param {number} degree the angle of the circle ** required when direction='circular' **
12 | * @param {array} buttonsList the nav items [{ onClick: click handler, src: 'for the icon'}]
13 | */
14 |
15 | import React, { Component } from 'react'
16 | import PropTypes from 'prop-types'
17 | import Toggler from './components/Toggler'
18 | import ButtonsList from './components/ButtonsList'
19 |
20 | class FloatingButtons extends Component {
21 |
22 | constructor(props){
23 | super(props)
24 | this.state = { isOpen: false }
25 | }
26 |
27 | toggleOpen = () => {
28 | this.setState((state) => ({
29 | ...state,
30 | isOpen: !state.isOpen
31 | }))
32 | }
33 |
34 | render() {
35 | const { dimension, direction, distance, degree, top, left, backgroundColor, buttonColor, buttonsList, itemBackgroundColor, buttonType } = this.props
36 | return (
37 |
38 |
46 |
55 |
56 | )
57 | }
58 | }
59 |
60 | FloatingButtons.defaultProps = {
61 | buttonType: 'hamburger',
62 | dimension: 40,
63 | top: 0,
64 | left: 0,
65 | backgroundColor: '#f8f9fa',
66 | itemBackgroundColor: '#f8f9fa',
67 | buttonColor: '#313131',
68 | direction: "circular",
69 | distance: 100,
70 | degree: 180,
71 | }
72 |
73 | FloatingButtons.propTypes = {
74 | buttonType: PropTypes.string,
75 | dimension: PropTypes.number,
76 | top: PropTypes.string,
77 | left: PropTypes.string,
78 | backgroundColor: PropTypes.string,
79 | itemBackgroundColor: PropTypes.string,
80 | buttonColor: PropTypes.string,
81 | distance: PropTypes.number,
82 | direction: PropTypes.string,
83 | degree: PropTypes.number,
84 | buttonsList: PropTypes.array.isRequired,
85 | }
86 |
87 | export default FloatingButtons
88 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | button.nav-toggler,
2 | button.menu-item {
3 | position: relative;
4 | opacity: 0.9;
5 | border: 1px solid rgba(0, 0, 0, 0.2);
6 | box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 2px 2px rgba(0, 0, 0, .15);
7 | transition: all 350ms cubic-bezier(0.25, 0, 0, 1);
8 | -webkit-transition: all 350ms cubic-bezier(0.25, 0, 0, 1);
9 | outline: none
10 | }
11 |
12 | button.nav-toggler:hover,
13 | button.menu-item:hover,
14 | button.nav-toggler.open {
15 | opacity: 1;
16 | box-shadow: none
17 | }
18 |
19 | button.menu-item:active{
20 | box-shadow: inset 0px 1px 2px 1px rgba(0, 0, 0, .08)
21 | }
22 |
23 | /* hamburger */
24 | button.nav-toggler.hamburger span:nth-child(1){
25 | position: relative;
26 | transform-origin: center left;
27 | }
28 |
29 | button.nav-toggler.hamburger.open span:nth-child(1){
30 | animation: open_1 350ms ease forwards running;
31 | }
32 |
33 | @keyframes open_1 {
34 | 0% {
35 | transform: rotate(0deg);
36 | left: 0%;
37 | top: 0%;
38 | }
39 |
40 | 100% {
41 | transform: rotate(45deg);
42 | left: 12.5%;
43 | top: -2.5%;
44 | }
45 | }
46 |
47 | button.nav-toggler.hamburger.close span:nth-child(1){
48 | animation: close_1 350ms ease forwards running;
49 | }
50 |
51 | @keyframes close_1 {
52 | 0% {
53 | transform: rotate(45deg);
54 | left: 12.5%;
55 | top: -2.5%;
56 | }
57 |
58 | 100% {
59 | transform: rotate(0deg);
60 | left: 0%;
61 | top: 0%;
62 | }
63 | }
64 |
65 | button.nav-toggler.hamburger span:nth-child(2){
66 | opacity: 1;
67 | transform-origin: center left;
68 | }
69 |
70 | button.nav-toggler.hamburger.open span:nth-child(2){
71 | animation: open_2 350ms ease forwards running;
72 | }
73 |
74 | @keyframes open_2 {
75 | 0% {
76 | transform: rotateY(0deg);
77 | opacity: 1;
78 | }
79 |
80 | 100% {
81 | transform: rotateY(90deg);
82 | opacity: 0;
83 | }
84 | }
85 |
86 | button.nav-toggler.hamburger.close span:nth-child(2){
87 | animation: close_2 350ms ease forwards running;
88 | }
89 |
90 | @keyframes close_2 {
91 | 0% {
92 | transform: rotateY(90deg);
93 | opacity: 0;
94 | }
95 |
96 | 100% {
97 | transform: rotateY(0deg);
98 | opacity: 1;
99 | }
100 | }
101 |
102 | button.nav-toggler.hamburger span:nth-child(3){
103 | position: relative;
104 | transform-origin: center left;
105 | }
106 |
107 | button.nav-toggler.hamburger.open span:nth-child(3){
108 | animation: open_3 350ms ease forwards running;
109 | }
110 |
111 | @keyframes open_3 {
112 | 0% {
113 | transform: rotate(0deg);
114 | left: 0%;
115 | top: 0%;
116 | }
117 |
118 | 100% {
119 | transform: rotate(-45deg);
120 | left: 12.5%;
121 | top: 2.5%;
122 | }
123 | }
124 |
125 | button.nav-toggler.hamburger.close span:nth-child(3){
126 | animation: close_3 350ms ease forwards running;
127 | }
128 |
129 | @keyframes close_3 {
130 | 0% {
131 | transform: rotate(-45deg);
132 | left: 12.5%;
133 | top: 2.5%;
134 | }
135 |
136 | 100%{
137 | transform: rotate(0deg);
138 | left: 0%;
139 | top: 0%;
140 | }
141 | }
142 |
143 | /* plus */
144 | button.nav-toggler.plus span:nth-child(1){
145 | position: relative;
146 | }
147 |
148 | button.nav-toggler.plus span:nth-child(2){
149 | display: none !important
150 | }
151 |
152 | button.nav-toggler.plus span:nth-child(3){
153 | position: relative;
154 | transform: rotate(90deg)
155 | }
156 |
157 | button.nav-toggler.plus.open {
158 | transform: rotate(135deg);
159 | }
160 |
161 | button.nav-toggler.plus.close {
162 | transform: rotate(0deg);
163 | }
164 |
165 | /* vert-dots */
166 | button.nav-toggler.vert-dots span:nth-child(1){
167 | position: relative;
168 | }
169 |
170 | button.nav-toggler.vert-dots span:nth-child(2){
171 | position: relative
172 | }
173 |
174 | button.nav-toggler.vert-dots span:nth-child(3){
175 | position: relative;
176 | transform: rotate(90deg)
177 | }
178 |
179 | /* hori-dots */
180 | button.nav-toggler.hori-dots span {
181 | position: absolute !important;
182 | }
183 |
184 | button.nav-toggler.hori-dots span:nth-child(1){
185 | left: 25%
186 | }
187 |
188 | button.nav-toggler.hori-dots span:nth-child(3){
189 | right: 25%
190 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-floating-buttons
2 |
3 | > A lightweight, customizable and animated ⚛️ react floating buttons
4 |
5 | [](https://www.npmjs.com/package/react-floating-buttons) [](https://standardjs.com)
6 |
7 | ## Install
8 |
9 | ```bash
10 | npm install --save react-floating-buttons
11 | ```
12 |
13 | ## Usage
14 |
15 | ```jsx
16 | import React, { Component } from 'react'
17 |
18 | import FloatingButtons from 'react-floating-buttons'
19 |
20 | import icon1 from './assets/icon1.svg'
21 | import icon2 from './assets/icon2.svg'
22 |
23 | class Example extends Component {
24 |
25 | this.buttonsList = [
26 | { onClick: ()=> alert('clicked icon1'), src: icon1 },
27 | { onClick: ()=> alert('clicked icon2'), src: icon2 },
28 | ]
29 |
30 | render() {
31 | return
32 | }
33 | }
34 | ```
35 |
36 |
37 |
38 | ## Props
39 |
40 |
41 |
42 | ##### buttonType
43 |
44 | `type: string, isRequired: false, default: 'hamburger'`
45 |
46 | The toggler button type, accepts: hamburger, plus, vert-dots or hori-dots
47 |
48 | ##### dimension
49 |
50 | `type: number, isRequired: false, default: 40`
51 |
52 | The buttons dimensions { width, height }
53 |
54 | ##### top
55 |
56 | `type: number | string, isRequired: false, default: 0`
57 |
58 | The offsetTop position of the toggler button
59 |
60 | ##### left
61 |
62 | `type: number | string, isRequired: false, default: 0`
63 |
64 | The offsetLeft position of the toggler button
65 |
66 | ##### backgroundColor
67 |
68 | `type: string, isRequired: false, default: '#f8f9fa'`
69 |
70 | The toggler button background color
71 |
72 | ##### buttonColor
73 |
74 | `type: string, isRequired: false, default: '#313131'`
75 |
76 | The toggler button color ( the dots color, the plus color ... )
77 |
78 | ##### itemBackgroundColor
79 |
80 | `type: string, isRequired: false, default: '#f8f9fa'`
81 |
82 | The toggler button color ( the dots color, the plus color ... )
83 |
84 | ##### direction
85 |
86 | `type: string, isRequired: false, default: 'circular'`
87 |
88 | The direction of the nav when opened, accepts: left, right, up, down or circular
89 |
90 | ##### distance
91 |
92 | `type: number, isRequired: false, default: 100`
93 |
94 | The distance between the toggler button and the buttons, used only with `direction='circular'`
95 |
96 | ##### degree
97 |
98 | `type: number, isRequired: false, default: 180`
99 |
100 | The angle of the circle, used only with `direction='circular'`
101 |
102 | ##### buttonsList
103 |
104 | `type: [{ onClick: () => {}, src: '' }], isRequired: true`
105 |
106 | The buttons item, a list of object where each object must have: `onClick` function to handle on click button and `src` the source of the icon of that button.
107 |
108 |
109 |
110 | ## Examples
111 |
112 | To run the exemple you need to:
113 |
114 | - clone this repo: `git clone https://github.com/AM-77/react-floating-buttons.git`
115 | - inside the react-floating-buttons folder run: `yarn` or `npm i`
116 | - inside the example folder run: `yarn` or `npm i` then run: `yarn start` or `npm start`
117 |
118 |
119 |
120 | ## Screenshots
121 |
122 | **circular 360 degree**
123 |
124 | 
125 |
126 |
127 |
128 | **circular down 180 degree**
129 |
130 | 
131 |
132 | **circular customizable degree**
133 |
134 | 
135 |
136 | 
137 |
138 | **direction right**
139 |
140 | 
141 |
142 |
143 |
144 | **direction left**
145 |
146 | 
147 |
148 | **customized background & direction up**
149 |
150 | 
151 |
152 |
153 |
154 | ## Contribution
155 |
156 | Feel free to raise an [Issue](https://github.com/AM-77/react-floating-buttons/issues) or submit a [PR](https://github.com/AM-77/react-floating-buttons/pulls).
157 |
158 |
159 | ## Inspired From
160 |
161 | [na6im/react-floating-button](https://github.com/na6im/react-floating-button)
162 |
163 |
164 | ## Copyright and license
165 |
166 | Code copyright 2019 AM-77. Code released under [MIT license](https://github.com/AM-77/react-floating-buttonss/blob/master/LICENSE).
167 |
--------------------------------------------------------------------------------
/src/components/Button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Button extends Component {
5 |
6 | constructor(props){
7 | super(props)
8 | const { dimension, itemBackgroundColor, index } = this.props
9 | this.initButtonStyle = {
10 | display: 'flex',
11 | justifyContent: 'center',
12 | alignItems: 'center',
13 | height: `${dimension}px`,
14 | width: `${dimension}px`,
15 | backgroundColor: itemBackgroundColor,
16 | borderRadius: `${dimension * 0.5}px`,
17 | padding: `${(dimension * 0.25)}px`,
18 | cursor: 'pointer',
19 | position: 'absolute',
20 | left: 0,
21 | top: 0,
22 | zIndex: 1,
23 | opacity: 0,
24 | borderWidth: '1px',
25 | borderColor: 'rgba(0, 0, 0, 0.2)',
26 | boxShadow: '0 0 0 1px rgba(0, 0, 0, .08), 0 2px 2px rgba(0, 0, 0, .15)',
27 | outline: 'none',
28 | transition: `all ${index * 50 + 200}ms cubic-bezier(0.71, 0.71, 0, 1.18) 0ms`,
29 | }
30 |
31 | this.imgStyle = {
32 | width: '100%',
33 | height: '100%'
34 | }
35 |
36 | this.state = {
37 | buttonStyle: {...this.initButtonStyle},
38 | }
39 | }
40 |
41 | componentDidUpdate(prevProps){
42 | if(prevProps !== this.props){
43 | let { dimension, index, nbrItems, isOpen, direction, degree, distance } = this.props
44 |
45 | if(isOpen){
46 |
47 | let translateX = 0, translateY = 0
48 | if(direction === 'up') translateY = -((dimension + dimension * 0.33) * (index + 1 ))
49 | else if(direction === 'down') translateY = ((dimension + dimension * 0.33) * (index + 1 ))
50 | else if(direction === 'right') translateX = ((dimension + dimension * 0.33) * (index + 1 ))
51 | else if(direction === 'left') translateX = -((dimension + dimension * 0.33) * (index + 1 ))
52 | else {
53 | if(!distance) distance = dimension * 2
54 |
55 | let angle = degree / nbrItems
56 | if(degree >= 360) angle = 360 / ( nbrItems + 1 )
57 |
58 | let radian = angle * (Math.PI / 180)
59 | translateX = (Math.cos(( radian )* index) * distance)
60 | translateY = (Math.sin(( radian )* index) * distance)
61 | }
62 |
63 | this.setState((state) => ({
64 | ...state,
65 | buttonStyle:{
66 | ...state.buttonStyle,
67 | opacity: "1",
68 | transform: `translate(${translateX}px, ${translateY}px)`
69 | }
70 | }))
71 | }else{
72 | this.setState((state) => ({
73 | ...state,
74 | buttonStyle:{
75 | ...state.buttonStyle,
76 | opacity: "0",
77 | transform: `translate(0px, 0px)`
78 | }
79 | }))
80 | }
81 | }
82 | }
83 |
84 | mouseEnter = () => {
85 | const { isOpen } = this.props
86 | if(isOpen)
87 | this.setState((state) => ({
88 | ...state,
89 | buttonStyle: {
90 | ...state.buttonStyle,
91 | boxShadow: 'none'
92 | }
93 | }))
94 | }
95 |
96 | mouseLeave = () => {
97 | this.setState((state) => ({
98 | ...state,
99 | buttonStyle: {
100 | ...state.buttonStyle,
101 | boxShadow: '0 0 0 1px rgba(0, 0, 0, .08), 0 2px 2px rgba(0, 0, 0, .15)'
102 | }
103 | }))
104 | }
105 |
106 | pointerDown = () => {
107 | this.setState((state) => ({
108 | ...state,
109 | buttonStyle: {
110 | ...state.buttonStyle,
111 | boxShadow: 'inset 0px 1px 2px 1px rgba(0, 0, 0, .15)'
112 | }
113 | }))
114 | }
115 |
116 | pointerUp = () => {
117 | this.setState((state) => ({
118 | ...state,
119 | buttonStyle: {
120 | ...state.buttonStyle,
121 | boxShadow: 'none'
122 | }
123 | }))
124 | }
125 |
126 | render() {
127 | const {onClick, src} = this.props
128 | return ()
137 | }
138 | }
139 |
140 | Button.defaultProps = {
141 | onClick: () => {},
142 | }
143 |
144 | Button.propTypes = {
145 | distance: PropTypes.number,
146 | degree: PropTypes.number.isRequired,
147 | dimension: PropTypes.number.isRequired,
148 | itemBackgroundColor: PropTypes.string,
149 | onClick: PropTypes.func,
150 | src: PropTypes.string.isRequired,
151 | index: PropTypes.number.isRequired,
152 | nbrItems: PropTypes.number.isRequired,
153 | isOpen: PropTypes.bool.isRequired,
154 | direction: PropTypes.string.isRequired,
155 | }
156 |
157 | export default Button
--------------------------------------------------------------------------------
/src/components/Toggler.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Toggler extends Component {
5 |
6 | constructor(props){
7 | super(props)
8 | const { dimension, backgroundColor, buttonColor } = this.props
9 |
10 | this.initButtonStyle = {
11 | display: 'flex',
12 | flexFlow: 'column wrap',
13 | justifyContent: 'space-around',
14 | alignItems: 'center',
15 | height: `${dimension}px`,
16 | width: `${dimension}px`,
17 | backgroundColor,
18 | borderRadius: `${dimension * 0.5}px`,
19 | padding: `${(dimension * 0.2) - 1}px`,
20 | cursor: 'pointer',
21 | zIndex: 2,
22 | position: "relative",
23 | opacity: "0.9",
24 | borderWidth: '1px',
25 | borderColor: 'rgba(0, 0, 0, 0.2)',
26 | boxShadow: '0 0 0 1px rgba(0, 0, 0, .08), 0 2px 2px rgba(0, 0, 0, .15)',
27 | transition: 'all 350ms cubic-bezier(0.25, 0, 0, 1)',
28 | outline: 'none'
29 | }
30 |
31 | this.initSpanStyle = {
32 | display: 'block',
33 | backgroundColor: buttonColor,
34 | width: `${dimension * .6}px`,
35 | height: `${dimension * 0.075}px`,
36 | borderRadius: `${(dimension * 0.075) / 2}px`,
37 | transformOrigin: 'center left',
38 | transition: 'all 350ms cubic-bezier(0.25, 0, 0, 1)',
39 | position: 'relative'
40 | }
41 |
42 | this.state = {
43 | buttonStyle: this.initButtonStyle,
44 | spanStyle: this.initSpanStyle,
45 | spanOneStyle: {},
46 | spanTwoStyle: {},
47 | spanThreeStyle: {}
48 | }
49 |
50 | }
51 |
52 | componentDidMount() {
53 | const { dimension, buttonType } = this.props
54 |
55 | if(buttonType === 'plus'){
56 | this.setState((state) => ({
57 | ...state,
58 | spanStyle:{
59 | ...state.spanStyle,
60 | width: (dimension * .5),
61 | height: dimension * 0.075,
62 | position: 'absolute',
63 | transformOrigin: 'center center'
64 | },
65 | spanTwoStyle:{ display: 'none'},
66 | spanThreeStyle:{ transform: 'rotate(90deg)' }
67 | }))
68 | }else{
69 | if(buttonType === 'hori-dots' || buttonType === 'vert-dots'){
70 | this.setState((state) => ({
71 | ...state,
72 | spanStyle:{
73 | ...state.spanStyle,
74 | width: dimension * 0.1,
75 | height: dimension * 0.1,
76 | borderRadius: dimension * 0.1
77 | }
78 | }))
79 |
80 | if(buttonType === 'hori-dots'){
81 | this.setState((state) => ({
82 | spanStyle:{
83 | ...state.spanStyle,
84 | position: 'absolute'
85 | },
86 | spanOneStyle:{
87 | left: '25%'
88 | },
89 | spanThreeStyle:{
90 | right: '25%'
91 | }
92 | }))
93 | }
94 | }
95 | }
96 |
97 | }
98 |
99 | toggleOpen = () => {
100 | const { buttonType, toggleOpen } = this.props
101 | toggleOpen()
102 |
103 | if(buttonType === 'plus'){
104 | this.animatePlusButton()
105 | }else if(buttonType === 'hamburger'){
106 | this.animateHmburgerButton()
107 | }
108 | }
109 |
110 | animatePlusButton = () => {
111 | const { isOpen } = this.props
112 | if(!isOpen){
113 | this.setState((state) => ({
114 | ...state,
115 | buttonStyle: {
116 | ...state.buttonStyle,
117 | transform: 'rotate(135deg)'
118 | }
119 | }))
120 | }else{
121 | this.setState((state) => ({
122 | ...state,
123 | buttonStyle: {
124 | ...state.buttonStyle,
125 | transform: 'rotate(0deg)'
126 | }
127 | }))
128 | }
129 | }
130 |
131 | animateHmburgerButton = () => {
132 | const { isOpen } = this.props
133 | if(!isOpen){
134 | this.setState((state) => ({
135 | ...state,
136 | spanOneStyle:{
137 | transform: 'rotate(45deg)',
138 | left: '12.5%',
139 | top: '-2.5%'
140 | },
141 | spanTwoStyle:{
142 | transform: 'rotateY(90deg)',
143 | opacity: 0
144 | },
145 | spanThreeStyle:{
146 | transform: 'rotate(-45deg)',
147 | left: '12.5%',
148 | top: '2.5%'
149 | }
150 | }))
151 | }else{
152 | this.setState((state) => ({
153 | ...state,
154 | spanOneStyle:{
155 | transform: 'rotate(0deg)',
156 | left: '0%',
157 | top: '0%'
158 | },
159 | spanTwoStyle:{
160 | transform: 'rotateY(0deg)',
161 | opacity: 1
162 | },
163 | spanThreeStyle:{
164 | transform: 'rotate(0deg)',
165 | left: '0%',
166 | top: '0%'
167 | }
168 | }))
169 | }
170 | }
171 |
172 | mouseEnter = () => {
173 | this.setState((state) => ({
174 | ...state,
175 | buttonStyle: {
176 | ...state.buttonStyle,
177 | opacity: '1',
178 | boxShadow: 'none'
179 | }
180 | }))
181 | }
182 |
183 | mouseLeave = () => {
184 | const { isOpen } = this.props
185 | if(isOpen){
186 | this.setState((state) => ({
187 | ...state,
188 | buttonStyle: { ...state.buttonStyle}
189 | }))
190 | }else{
191 | this.setState((state) => ({
192 | ...state,
193 | buttonStyle: { ...this.initButtonStyle}
194 | }))
195 | }
196 | }
197 |
198 | render() {
199 | return ( )
208 | }
209 | }
210 |
211 | Toggler.defaultProps = {
212 | dimension: 80,
213 | backgroundColor: '#f8f9fa',
214 | buttonColor: '#313131'
215 | }
216 |
217 | Toggler.propTypes = {
218 | buttonType: PropTypes.string.isRequired,
219 | toggleOpen: PropTypes.func.isRequired,
220 | isOpen: PropTypes.bool.isRequired,
221 | dimension: PropTypes.number,
222 | backgroundColor: PropTypes.string,
223 | buttonColor: PropTypes.string
224 | }
225 |
226 | export default Toggler
227 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FloatingButtons from 'react-floating-buttons'
3 |
4 | import instagram from './assets/instagram.svg'
5 | import github from './assets/github.svg'
6 | import dribbble from './assets/dribbble.svg'
7 | import linkedin from './assets/linkedin.svg'
8 | import medium from './assets/medium.svg'
9 | import spotify from './assets/spotify.svg'
10 | import twitter from './assets/twitter.svg'
11 |
12 |
13 | function App () {
14 |
15 | const buttonsList = [
16 | { onClick: ()=> alert('clicked instagram'), src: instagram },
17 | { onClick: ()=> alert('clicked medium'), src: medium },
18 | { onClick: ()=> alert('clicked dribbble'), src: dribbble },
19 | { onClick: ()=> alert('clicked github'), src: github },
20 | { onClick: ()=> alert('clicked linkedin'), src: linkedin },
21 | { onClick: ()=> alert('clicked spotify'), src: spotify },
22 | { onClick: ()=> alert('clicked twitter'), src: twitter },
23 | ]
24 |
25 | return (<>
26 | react-floating-buttons exemples
27 |
28 |
29 |
Horizantal floating button
30 |
31 |
32 |
33 | {` `}
40 |
41 |
42 |
50 |
51 |
52 |
53 |
54 |
55 | {` `}
62 |
63 |
64 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
vertical floating button
78 |
79 |
80 |
81 | {` `}
91 |
92 |
93 |
103 |
104 |
105 |
106 |
107 |
108 | {` `}
118 |
119 |
120 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
circular floating button
138 |
139 |
140 |
141 | {` `}
150 |
151 |
152 |
161 |
162 |
163 |
164 |
165 |
166 | {` `}
174 |
175 |
176 |
184 |
185 |
186 |
187 |
188 |
189 | {` `}
197 |
198 |
199 |
207 |
208 |
209 |
210 |
211 |
212 | {` `}
221 |
222 |
223 |
232 |
233 |
234 |
235 |
236 |
237 | {` `}
246 |
247 |
248 |
257 |
258 |
259 |
260 |
261 |
262 | {` `}
271 |
272 |
273 |
274 |
283 |
284 |
285 |
286 |
287 |
288 |
289 | >)
290 | }
291 |
292 | export default App
293 |
--------------------------------------------------------------------------------