├── examples
├── .babelrc
├── src
│ ├── .babelrc
│ ├── popup_text.js
│ ├── index.js
│ └── popup_table.js
├── index.html
├── popup_text.html
├── popup_table.html
├── package.json
├── webpack.config.babel.js
└── css
│ └── style.css
├── .DS_Store
├── assets
└── record.gif
├── src
├── index.js
├── .babelrc
├── PopupText.js
├── PopupTable.scss
├── PopupMenu.scss
├── PopupMenu.js
└── PopupTable.js
├── test
├── .babelrc
├── PopupText.test.js
├── PopupTable.test.js
├── PopupMenu.test.js
└── __snapshots__
│ ├── PopupText.test.js.snap
│ ├── PopupTable.test.js.snap
│ └── PopupMenu.test.js.snap
├── .babelrc
├── .travis.yml
├── LICENSE
├── .gitignore
├── rollup.config.babel.js
├── package.json
└── README.md
/examples/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015"]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasha240100/react-rectangle-popup-menu/HEAD/.DS_Store
--------------------------------------------------------------------------------
/assets/record.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sasha240100/react-rectangle-popup-menu/HEAD/assets/record.gif
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './PopupMenu';
2 | export * from './PopupTable';
3 | export * from './PopupText';
4 |
--------------------------------------------------------------------------------
/test/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"],
3 | "plugins": [
4 | "transform-class-properties",
5 | "transform-object-rest-spread"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}]
4 | ],
5 | "env": {
6 | "test": {
7 | "presets": ["es2015", "react"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}],
4 | "react"
5 | ],
6 | "plugins": [
7 | "transform-class-properties",
8 | "transform-object-rest-spread"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}],
4 | "react"
5 | ],
6 | "plugins": [
7 | "transform-class-properties",
8 | "transform-object-rest-spread",
9 | "external-helpers"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/PopupText.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 |
3 | export class PopupText extends Component {
4 | render() {
5 | return (
6 |
7 | {this.props.children}
8 |
9 | )
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "7"
4 |
5 | install:
6 | - npm install
7 |
8 | script:
9 | - npm run build
10 |
11 | deploy:
12 | provider: pages
13 | skip-cleanup: true
14 | github-token: $GITHUB_TOKEN # Set in travis-ci.org dashboard, marked secure
15 | keep-history: true
16 | on:
17 | branch: master
18 |
--------------------------------------------------------------------------------
/test/PopupText.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {PopupMenu, PopupText} from '../build/rrpm.module';
3 | import renderer from 'react-test-renderer';
4 |
5 | test('PopupTable', () => {
6 | const component = renderer.create(
7 |
8 | Test
9 |
10 | );
11 |
12 | let tree = component.toJSON();
13 | expect(tree).toMatchSnapshot();
14 | });
15 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/PopupTable.scss:
--------------------------------------------------------------------------------
1 | .PopupTable {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-around;
5 | width: calc(200px - 10px);
6 | flex-flow: row wrap;
7 | // align-content: flex-start;
8 | }
9 |
10 | .item {
11 | padding: 5px;
12 | border-radius: 2px;
13 | transition: background 0.15s ease-in-out;
14 |
15 | &:hover {
16 | background: rgba(0,0,0,0.1);
17 | }
18 | }
19 |
20 | .placeholder {
21 | padding: 0px;
22 | height: 10px;
23 | display: block;
24 | border-radius: 2px;
25 | visibility: hidden;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/popup_text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/examples/popup_table.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/PopupTable.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {PopupMenu, PopupTable} from '../build/rrpm.module';
3 | import renderer from 'react-test-renderer';
4 |
5 | test('PopupTable', () => {
6 | const component = renderer.create(
7 |
8 |
9 | 1
10 | 2
11 | 3
12 | 4
13 |
14 |
15 | );
16 |
17 | let tree = component.toJSON();
18 | expect(tree).toMatchSnapshot();
19 | });
20 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "babel-loader": "^7.1.2",
13 | "babel-preset-react": "^6.24.1",
14 | "babel-register": "^6.26.0",
15 | "express": "^4.16.2",
16 | "webpack": "^3.11.0",
17 | "webpack-dev-server": "^2.11.1"
18 | },
19 | "dependencies": {
20 | "font-awesome": "^4.7.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/src/popup_text.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import FontAwesome from 'react-fontawesome';
4 | import {PopupMenu, PopupText} from 'react-rectangle-popup-menu';
5 |
6 | import 'font-awesome/css/font-awesome.css';
7 |
8 | const button = ();
9 |
10 | const Application = () => (
11 |
12 |
13 | Some text
14 |
15 |
16 | );
17 |
18 | ReactDOM.render(
19 | ,
20 | document.getElementById('app')
21 | )
22 |
--------------------------------------------------------------------------------
/test/PopupMenu.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {PopupMenu} from '../build/rrpm.module';
3 | import renderer from 'react-test-renderer';
4 |
5 | describe('PopupMenu', () => {
6 | const component = renderer.create(
7 |
8 | );
9 |
10 | let tree;
11 |
12 | it('should render correctly', () => {
13 | tree = component.toJSON();
14 | expect(tree).toMatchSnapshot();
15 | });
16 |
17 | // Hover button
18 | it('should respond to mouse hover', () => {
19 | tree.children[0].props.onMouseOver();
20 | tree = component.toJSON();
21 | expect(tree).toMatchSnapshot();
22 | });
23 |
24 | // Unhover button
25 | it('should respond to mouse unhover', () => {
26 | tree.children[0].props.onMouseOut();
27 | tree = component.toJSON();
28 | expect(tree).toMatchSnapshot();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/test/__snapshots__/PopupText.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PopupTable 1`] = `
4 |
7 |
12 |
25 |
32 |
33 | Test
34 |
35 |
36 |
37 |
38 | `;
39 |
--------------------------------------------------------------------------------
/examples/src/index.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react';
2 | import ReactDOM from 'react-dom';
3 | import FontAwesome from 'react-fontawesome';
4 | import {PopupMenu, PopupTable} from 'react-rectangle-popup-menu';
5 |
6 | import 'font-awesome/css/font-awesome.css';
7 |
8 | const button = ();
9 |
10 | const Application = () => (
11 |
12 |
18 |
19 |
20 | );
21 |
22 | window.addEventListener('load', () => {
23 | const iframe = document.querySelector('iframe');
24 |
25 | for (let link of document.querySelectorAll('a.link')) {
26 | link.addEventListener('click', (e) => {
27 | iframe.src = e.target.href.replace('#', '') + '.html';
28 | })
29 | }
30 | })
31 |
32 | ReactDOM.render(
33 | ,
34 | document.getElementById('app')
35 | )
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Alexander Buzin
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 |
--------------------------------------------------------------------------------
/rollup.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import babel from 'rollup-plugin-babel';
3 | import resolve from 'rollup-plugin-node-resolve';
4 | import commonjs from 'rollup-plugin-commonjs';
5 | import replace from 'rollup-plugin-replace';
6 | import postcss from 'rollup-plugin-postcss';
7 |
8 |
9 | export default {
10 | input: './src/index.js',
11 | moduleName: 'ReactRectanglePopupMenu',
12 | sourcemap: true,
13 |
14 | // output: {
15 | // file: './build/rrpm.js',
16 | // format: 'umd',
17 | // name: 'ReactRectanglePopupMenu',
18 | // sourcemap: true
19 | // },
20 |
21 | targets: [
22 | {
23 | dest: './build/rrpm.js',
24 | format: 'umd'
25 | },
26 | {
27 | dest: 'build/rrpm.module.js',
28 | format: 'es'
29 | }
30 | ],
31 |
32 | plugins: [
33 | postcss({
34 | modules: true
35 | }),
36 | babel({
37 | exclude: 'node_modules/**'
38 | }),
39 | replace({
40 | 'process.env.NODE_ENV': JSON.stringify('development')
41 | }),
42 | resolve(),
43 | commonjs()
44 | ],
45 |
46 | external: ['react', 'react-dom'],
47 |
48 | globals: {
49 | react: 'React',
50 | 'react-dom': 'ReactDOM'
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/examples/src/popup_table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import FontAwesome from 'react-fontawesome';
4 | import {PopupMenu, PopupTable} from 'react-rectangle-popup-menu';
5 |
6 | import 'font-awesome/css/font-awesome.css';
7 |
8 | const button = ();
9 |
10 | const Application = () => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | ReactDOM.render(
33 | ,
34 | document.getElementById('app')
35 | )
36 |
--------------------------------------------------------------------------------
/examples/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import express from 'express';
3 | import webpack from 'webpack';
4 |
5 | export default {
6 | entry: {
7 | index: './src/index.js',
8 | popup_table: './src/popup_table.js',
9 | popup_text: './src/popup_text.js'
10 | },
11 |
12 | output: {
13 | filename: '[name].bundle.js',
14 | path: path.resolve(__dirname, './bundle/')
15 | },
16 |
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | exclude: /(node_modules|bower_components)/,
22 | use: {
23 | loader: 'babel-loader'
24 | }
25 | },
26 | {
27 | test: /\.css$/,
28 | use: [ 'style-loader', 'css-loader' ]
29 | },
30 | {
31 | test: /\.(eot|svg|ttf|woff|woff2)$/,
32 | use: [ 'url-loader' ]
33 | }
34 | ]
35 | },
36 |
37 | externals: {
38 | 'react-rectangle-popup-menu': 'window.ReactRectanglePopupMenu',
39 | react: 'window.React',
40 | 'react-dom': 'window.ReactDOM'
41 | },
42 |
43 | plugins: [
44 | new webpack.DefinePlugin({
45 | 'process.env.NODE_ENV': JSON.stringify('development')
46 | })
47 | ],
48 |
49 | devServer: {
50 | contentBase: './',
51 | publicPath: '/build',
52 | port: 8080,
53 | before(app, server) {
54 | app.use('/build', express.static(path.resolve(__dirname, '../build/')))
55 | }
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/test/__snapshots__/PopupTable.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PopupTable 1`] = `
4 |
7 |
12 |
25 |
33 |
36 |
37 | 1
38 |
39 |
40 |
43 |
44 | 2
45 |
46 |
47 |
50 |
51 | 3
52 |
53 |
54 |
57 |
58 | 4
59 |
60 |
61 |
62 |
63 |
64 | `;
65 |
--------------------------------------------------------------------------------
/examples/css/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Oxygen');
2 |
3 | body {
4 | background: #F0CA4D;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .centered {
10 | position: absolute;
11 | left: calc(50% - 15px);
12 | top: 30%;
13 | }
14 |
15 | #sidebar {
16 | background: transparent;
17 | position: fixed;
18 | width: 100%;
19 | height: 120px;
20 | left: 0;
21 | padding: 50px 60px;
22 | /* border-top: 6px solid #3e3e3e0d; */
23 | z-index: 1;
24 | bottom: 0;
25 | }
26 |
27 | #sidebar ul {
28 | padding: 0;
29 | list-style: none;
30 | width: 230px;
31 | }
32 |
33 | #sidebar li {
34 | background: #F0CA4D;
35 | padding: 5px 10px;
36 | border-radius: 5px;
37 | transition: all 0.3s linear;
38 | }
39 |
40 | #sidebar li:hover {
41 | background: #f0b64d;
42 | }
43 |
44 | #sidebar a {
45 | color: white;
46 | text-decoration: none;
47 | font-family: 'Oxygen', sans-serif;
48 | font-size: 16pt;
49 | line-height: 2;
50 | letter-spacing: 0.3px;
51 | }
52 |
53 | #sidebar a:before {
54 | content: "[/]";
55 | text-shadow: 1px 1px rgba(0,0,0,0.2);
56 | vertical-align: text-bottom;
57 | display: inline;
58 | color: #eee;
59 | font-size: 10pt;
60 | padding-right: 10px;
61 | }
62 |
63 | #sidebar a:after {
64 | content: " (class)";
65 | text-shadow: 0.5px 0.5px #aaa;
66 | color: #333333;
67 | font-size: 12pt;
68 | padding-left: 10px;
69 | }
70 |
71 | iframe {
72 | border: none;
73 | position: fixed;
74 | top: 0;
75 | right: 0;
76 | left: 0;
77 | height: calc(100% - 220px);
78 | width: 100%;
79 | }
80 |
--------------------------------------------------------------------------------
/test/__snapshots__/PopupMenu.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`PopupMenu should render correctly 1`] = `
4 |
26 | `;
27 |
28 | exports[`PopupMenu should respond to mouse hover 1`] = `
29 |
51 | `;
52 |
53 | exports[`PopupMenu should respond to mouse unhover 1`] = `
54 |
76 | `;
77 |
--------------------------------------------------------------------------------
/src/PopupMenu.scss:
--------------------------------------------------------------------------------
1 | .PopupMenu {
2 | position: relative;
3 | }
4 |
5 | .button {
6 | padding: 5px;
7 | border-radius: 2px;
8 | width: 30px;
9 | height: 30px;
10 | transition: background 0.25s ease-in-out;
11 |
12 | &:hover, &:global(.active) {
13 | background: rgba(0,0,0,0.1);
14 | }
15 | }
16 |
17 | .popover {
18 | background: white;
19 | position: absolute;
20 | width: calc(200px - 10px);
21 | height: calc(200px - 10px);
22 | left: calc(-100px + 50%);
23 | top: 60px;
24 | border-radius: 5px;
25 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
26 | transition: opacity 0.5s ease-in-out;
27 | opacity: 0;
28 | padding: 5px;
29 | z-index: 1;
30 |
31 | &:global(.active) {
32 | opacity: 1;
33 | }
34 |
35 | &:hover + .button {
36 | background: rgba(0,0,0,0.1);
37 | }
38 |
39 | &:before {
40 | content: "";
41 | position: absolute;
42 | display: block;
43 | top: -20px;
44 | left: calc(50% - 10px);
45 | border: 10px solid white;
46 | border-color: transparent transparent white transparent;
47 | }
48 | }
49 |
50 | .direction-top {
51 | .popover {
52 | top: auto;
53 | bottom: 60px;
54 |
55 | &:before {
56 | top: auto;
57 | bottom: -20px;
58 | border-color: white transparent transparent transparent;
59 | }
60 | }
61 | }
62 |
63 | .direction-left {
64 | .popover {
65 | top: -100px;
66 | left: auto !important;
67 | right: 60px;
68 |
69 | &:before {
70 | left: auto;
71 | right: -20px;
72 | top: calc(50% - 10px);
73 | border-color: transparent transparent transparent white;
74 | }
75 | }
76 | }
77 |
78 | .direction-right {
79 | .popover {
80 | top: -100px;
81 | right: auto !important;
82 | left: 60px !important;
83 |
84 | &:before {
85 | right: auto;
86 | left: -20px;
87 | top: calc(50% - 10px);
88 | border-color: transparent white transparent transparent;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-rectangle-popup-menu",
3 | "version": "0.0.1",
4 | "description": "React rectangle popup menu component library",
5 | "main": "build/rrpm.js",
6 | "module": "build/rrpm.module.js",
7 | "scripts": {
8 | "build": "rollup -c rollup.config.babel.js",
9 | "watch": "rollup -c rollup.config.babel.js -w",
10 | "examples:build": "cd \"./examples\" && \"./node_modules/.bin/webpack\"",
11 | "examples:watch": "cd \"./examples\" && \"./node_modules/.bin/webpack-dev-server\"",
12 | "start": "(npm run watch & npm run examples:watch)",
13 | "test": "jest --no-cache"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/sasha240100/react-rectangle-popup-menu.git"
18 | },
19 | "keywords": [
20 | "react",
21 | "component",
22 | "library",
23 | "popup",
24 | "menu"
25 | ],
26 | "author": "Alexander Buzin",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/sasha240100/react-rectangle-popup-menu/issues"
30 | },
31 | "homepage": "https://github.com/sasha240100/react-rectangle-popup-menu#readme",
32 | "dependencies": {
33 | "clone": "^2.1.1",
34 | "prop-types": "^15.6.0",
35 | "react": "^16.2.0",
36 | "react-dom": "^16.2.0",
37 | "react-fontawesome": "^1.6.1"
38 | },
39 | "devDependencies": {
40 | "babel-cli": "^6.26.0",
41 | "babel-jest": "^22.4.0",
42 | "babel-plugin-external-helpers": "^6.22.0",
43 | "babel-plugin-transform-class-properties": "^6.24.1",
44 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
45 | "babel-preset-es2015": "^6.24.1",
46 | "babel-preset-react": "^6.24.1",
47 | "babel-register": "^6.26.0",
48 | "classnames": "^2.2.5",
49 | "css-loader": "^0.28.9",
50 | "jest": "^22.4.0",
51 | "node-sass": "^4.7.2",
52 | "postcss-scss": "^1.0.3",
53 | "react-test-renderer": "^16.2.0",
54 | "rollup": "^0.56.2",
55 | "rollup-plugin-babel": "^3.0.3",
56 | "rollup-plugin-commonjs": "^8.3.0",
57 | "rollup-plugin-node-resolve": "^3.0.3",
58 | "rollup-plugin-postcss": "^1.2.8",
59 | "rollup-plugin-replace": "^2.0.0",
60 | "rollup-plugin-serve": "^0.4.2",
61 | "rollup-watch": "^4.3.1",
62 | "style-loader": "^0.20.2",
63 | "url-loader": "^0.6.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-rectangle-popup-menu
2 | [](https://travis-ci.org/sasha240100/react-rectangle-popup-menu)
3 | [](https://www.npmjs.com/package/react-rectangle-popup-menu)
4 |
5 | [](https://www.npmjs.com/package/react-rectangle-popup-menu)
6 |
7 | React rectangle popup menu library. [Demo](https://sasha240100.github.io/react-rectangle-popup-menu/examples/)
8 |
9 | 
10 |
11 | ### ``
12 |
13 | ```js
14 | class Popup {
15 | render() {
16 |
17 | // Content
18 |
19 | }
20 | }
21 | ```
22 |
23 | #### Parameters for ``
24 |
25 | ```js
26 | {
27 | width: ?number = 200,
28 | height: ?(number | 'auto') = 'auto', // If auto it's minimized to rows size
29 | direction: ?('top' | 'bottom' | 'left' | 'right') = 'top',
30 | button: ReactNode
31 | }
32 | ```
33 |
34 | ### ``
35 |
36 | ```js
37 | import FontAwesome from 'react-fontawesome';
38 |
39 | const button = ();
40 |
41 | class Popup {
42 | render() {
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | }
61 | }
62 | ```
63 |
64 | #### Parameters for ``
65 |
66 | ```js
67 | {
68 | // Items per row (used to generate normal width of placeholder)
69 | rowItems: ?number = 1
70 | }
71 | ```
72 |
73 | ### ``
74 | ```js
75 | class Popup {
76 | render() {
77 |
78 | Some text
79 |
80 | }
81 | }
82 | ```
83 |
--------------------------------------------------------------------------------
/src/PopupMenu.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import PropTypes from 'prop-types';
3 | import cx from 'classnames';
4 | import clone from 'clone';
5 |
6 | import style from './PopupMenu.scss';
7 |
8 | export class PopupMenu extends Component {
9 | static defaultProps = {
10 | width: 200,
11 | height: 'auto'
12 | };
13 |
14 | state = {
15 | hovered: false,
16 | displayable: false
17 | };
18 |
19 | static childContextTypes = {
20 | popupWidth: PropTypes.number
21 | };
22 |
23 | getChildContext() {
24 | return {
25 | popupWidth: this.props.width
26 | };
27 | }
28 |
29 | constructor(props) {
30 | super(props);
31 | }
32 |
33 | hover = () => {
34 | clearTimeout(this._diplayTimeout);
35 | this.setState({hovered: true, displayable: true});
36 | };
37 |
38 | unhover = () => {
39 | this.setState({hovered: false});
40 | this._diplayTimeout = setTimeout(() => this.setState({displayable: false}), 500);
41 | };
42 |
43 | configureStyles(props) {
44 | return {
45 | popover: {
46 | width: `calc(${props.width}px - 10px)`,
47 | height: props.height === 'auto' ? 'auto' : `calc(${props.height}px - 10px)`,
48 | left: `calc(${-props.width / 2}px + 50%)`,
49 | ...(props.direction === 'left' || props.direction === 'right' ? {
50 | top: `calc(${-props.height / 2}px + 50%)`
51 | } : {})
52 | }
53 | };
54 | }
55 |
56 | render() {
57 | const {hovered, displayable} = this.state;
58 | const {direction} = this.props;
59 |
60 | const styles = this.configureStyles(this.props);
61 |
62 | return (
63 |
66 |
75 | {this.props.button}
76 |
77 |
91 | {this.props.children}
92 |
93 |
94 | )
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/PopupTable.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {render} from 'react-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | import style from './PopupTable.scss';
6 |
7 | const container = document.createElement('div');
8 | container.style.position = 'absolute';
9 | container.style.left = '-999999px';
10 | document.body.appendChild(container);
11 |
12 | const getSize = (element) => {
13 | const elementParent = document.createElement('div');
14 | container.appendChild(elementParent);
15 |
16 | return new Promise(resolve => {
17 | render(element, elementParent, () => {
18 | resolve({
19 | width: elementParent.firstChild.offsetWidth,
20 | height: elementParent.firstChild.offsetHeight
21 | });
22 | })
23 | });
24 | }
25 |
26 | export class PopupTable extends Component {
27 | constructor(props, context) {
28 | super(props, context);
29 |
30 | this.sizes = [];
31 | this.wait = [];
32 |
33 | this.updateLayout();
34 | }
35 |
36 | updateLayout() {
37 | this.items = this.props.children.length % this.props.rowItems;
38 |
39 | this.children = this.props.children.map((component) => {
40 | this.wait.push(getSize(component));
41 | return React.cloneElement(component);
42 | });
43 |
44 | Promise.all(this.wait).then((sizes) => {
45 | this.sizes = sizes;
46 | this.forceUpdate();
47 | });
48 | }
49 |
50 | static defaultProps = {
51 | rowItems: 1
52 | };
53 |
54 | static contextTypes = {
55 | popupWidth: PropTypes.number
56 | };
57 |
58 | render() {
59 | const {rowItems} = this.props;
60 | const {children, sizes, items} = this;
61 |
62 | if (sizes.length < 1) return null;
63 |
64 | const width = this.context.popupWidth || 200;
65 |
66 | if (children.length % rowItems !== 0) {
67 | const contentSize = sizes
68 | .slice(-items)
69 | .reduce((size, {width}) => size + width + 10, 0);
70 |
71 | const margin = (width - rowItems * (contentSize / items)) / (rowItems);
72 |
73 | children.push(
74 |
79 | );
80 | }
81 |
82 | return (
83 |
87 | {children.map((child, i) => (
88 |
0 ? style.placeholder : style.item}>
89 | {child}
90 |
91 | ))}
92 |
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------