├── .eslintrc
├── .gitignore
├── .npmignore
├── .stylintrc
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── docs
├── 0b39d1e64af23fe5437238402d08496a.svg
├── 35071d00819547a959ef3450c129d77e.eot
├── 37f4597594857b017901209aae0a60e1.svg
├── 3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf
├── 81436770636b45508203b3022075ae73.ttf
├── 8a53d21a4d9aa1aac2bf15093bd748c4.woff
├── af39e41be700d6148c61a6c1ffc84215.svg
├── build
│ └── bundle.55aecd75.js
├── bundle.js
├── bundle.js.map
├── d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff
├── d9c6d360d27eac625da0405245ec9f0d.eot
└── index.html
├── package.json
├── setupTests.js
├── src
├── Repeatable.jsx
└── index.js
├── styleguide.config.js
├── styleguide
├── components
│ ├── Progress.jsx
│ ├── StyleGuideRenderer.jsx
│ └── Wrapper.jsx
├── examples
│ └── README.md
├── setup.js
└── styles.css
├── test
└── index.js
└── webpack.config.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "trendmicro",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | package-lock.json
4 | /.nyc_output
5 | /coverage
6 | /dist
7 | /lib
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | /.nyc_output
3 | /coverage
4 |
--------------------------------------------------------------------------------
/.stylintrc:
--------------------------------------------------------------------------------
1 | //
2 | // https://github.com/rossPatton/stylint
3 | //
4 | {
5 | "blocks": false,
6 | "brackets": "always",
7 | "colons": "always",
8 | "colors": false,
9 | "commaSpace": "always",
10 | "commentSpace": false,
11 | "cssLiteral": "never",
12 | "depthLimit": false,
13 | "duplicates": false,
14 | "efficient": "always",
15 | "extendPref": false,
16 | "globalDupe": false,
17 | "indentPref": false,
18 | "leadingZero": "never",
19 | "maxErrors": false,
20 | "maxWarnings": false,
21 | "mixed": false,
22 | "namingConvention": false,
23 | "namingConventionStrict": false,
24 | "none": "never",
25 | "noImportant": true,
26 | "parenSpace": false,
27 | "placeholders": "always",
28 | "prefixVarsWithDollar": "always",
29 | "quotePref": false,
30 | "semicolons": "always",
31 | "sortOrder": false,
32 | "stackedProperties": "never",
33 | "trailingWhitespace": "never",
34 | "universal": false,
35 | "valid": true,
36 | "zeroUnits": "never",
37 | "zIndexNormalize": false
38 | }
39 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | group: edge
4 |
5 | language: node_js
6 |
7 | os:
8 | - linux
9 |
10 | node_js:
11 | - '8'
12 | - '10'
13 |
14 | before_install:
15 | - npm install -g npm
16 | - npm --version
17 |
18 | after_success:
19 | - npm run coveralls
20 | - npm run coverage-clean
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present Cheton Wu
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 | # react-repeatable [](https://travis-ci.org/cheton/react-repeatable) [](https://coveralls.io/github/cheton/react-repeatable?branch=master)
2 |
3 | [](https://nodei.co/npm/react-repeatable/)
4 |
5 | A press and hold wrapper component that can trigger hold action multiple times while holding down.
6 |
7 | Demo: https://cheton.github.io/react-repeatable
8 |
9 | ## Installation
10 |
11 | ```
12 | npm install --save react-repeatable
13 | ```
14 |
15 | ## Usage
16 |
17 | ```jsx
18 | {
22 | // Callback fired when the mousedown or touchstart event is triggered.
23 | }}
24 | onHoldStart={() => {
25 | // Callback fired once before the first hold action.
26 | }}
27 | onHold={() => {
28 | // Callback fired mutiple times while holding down.
29 | }}
30 | onHoldEnd={() => {
31 | // Callback fired once after the last hold action.
32 | }}
33 | onRelease={(event) => {
34 | // Callback fired when the mouseup, touchcancel, or touchend event is triggered.
35 | }}
36 | >
37 | Press Me
38 |
39 | ```
40 |
41 | ### Repeatable Button
42 |
43 | ```jsx
44 | const RepeatableButton = ({ onClick, ...props }) => (
45 |
52 | );
53 |
54 |
55 | ```
56 |
57 | ## API
58 |
59 | ### Sequence of Events
60 |
61 | #### Hold action is occurred
62 | onPress -> onHoldStart -> onHold (once or more) -> onHoldEnd -> onRelease
63 |
64 | #### Hold action is not occurred
65 | onPress -> onRelease
66 |
67 | ### Properties
68 |
69 | Name | Type | Default | Description
70 | :--- | :--- | :------ | :----------
71 | tag | element | 'div' | A custom element for this component.
72 | disabled | Boolean | false | Set it to true to disable event actions.
73 | repeatDelay | Number | 500 | The time (in milliseconds) to wait before the first hold action is being triggered.
74 | repeatInterval | Number | 32 | The time interval (in milliseconds) on how often to trigger a hold action.
75 | repeatCount | Number | 0 | The number of times the hold action will take place. A zero value will disable the repeat counter.
76 | onPress | Function(event) | | Callback fired when the mousedown or touchstart event is triggered.
77 | onHoldStart | Function() | | Callback fired once before the first hold action.
78 | onHold | Function() | | Callback fired mutiple times while holding down.
79 | onHoldEnd | Function() | | Callback fired once after the last hold action.
80 | onRelease | Function(event) | | Callback fired when the mouseup, touchcancel, or touchend event is triggered.
81 |
82 | ## License
83 |
84 | MIT
85 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: '@trendmicro/babel-config',
3 | presets: [
4 | '@babel/preset-env',
5 | '@babel/preset-react'
6 | ]
7 | };
8 |
--------------------------------------------------------------------------------
/docs/35071d00819547a959ef3450c129d77e.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/35071d00819547a959ef3450c129d77e.eot
--------------------------------------------------------------------------------
/docs/37f4597594857b017901209aae0a60e1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
--------------------------------------------------------------------------------
/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf
--------------------------------------------------------------------------------
/docs/81436770636b45508203b3022075ae73.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/81436770636b45508203b3022075ae73.ttf
--------------------------------------------------------------------------------
/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff
--------------------------------------------------------------------------------
/docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff
--------------------------------------------------------------------------------
/docs/d9c6d360d27eac625da0405245ec9f0d.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cheton/react-repeatable/d3dfba4114992e9f97a41f095df41514b17bf578/docs/d9c6d360d27eac625da0405245ec9f0d.eot
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
React Component v2.0.0
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-repeatable",
3 | "version": "2.0.1",
4 | "description": "A press and hold wrapper component that can trigger hold action multiple times while holding down.",
5 | "main": "lib/index.js",
6 | "files": [
7 | "lib"
8 | ],
9 | "scripts": {
10 | "prepare": "npm run lint && npm test && npm run clean && npm run build && npm run styleguide:build",
11 | "build": "webpack-cli",
12 | "clean": "rm -f {lib,dist}/*",
13 | "demo": "http-server -p 8000 docs/",
14 | "lint": "npm run eslint",
15 | "eslint": "eslint --ext .js --ext .jsx *.js src test",
16 | "test": "tap test/*.js --node-arg=--require --node-arg=@babel/register --node-arg=--require --node-arg=@babel/polyfill",
17 | "coveralls": "tap test/*.js --coverage --coverage-report=text-lcov --nyc-arg=--require --nyc-arg=@babel/register --nyc-arg=--require --nyc-arg=@babel/polyfill | coveralls",
18 | "dev": "npm run styleguide",
19 | "styleguide": "styleguidist server",
20 | "styleguide:build": "styleguidist build"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/cheton/react-repeatable.git"
25 | },
26 | "author": "Cheton Wu ",
27 | "contributors": [
28 | {
29 | "name": "Cheton Wu",
30 | "email": "cheton@gmail.com",
31 | "url": "https://github.com/cheton"
32 | }
33 | ],
34 | "license": "MIT",
35 | "bugs": {
36 | "url": "https://github.com/cheton/react-repeatable/issues"
37 | },
38 | "homepage": "https://github.com/cheton/react-repeatable",
39 | "keywords": [
40 | "react",
41 | "repeatable",
42 | "press",
43 | "hold",
44 | "release",
45 | "click"
46 | ],
47 | "peerDependencies": {
48 | "react": "^0.14.0 || >=15.0.0"
49 | },
50 | "dependencies": {
51 | "chained-function": "^0.5.0",
52 | "prop-types": "^15.6.0"
53 | },
54 | "devDependencies": {
55 | "@babel/cli": "~7.5.5",
56 | "@babel/core": "~7.5.5",
57 | "@babel/polyfill": "~7.4.4",
58 | "@babel/preset-env": "~7.5.5",
59 | "@babel/preset-react": "~7.0.0",
60 | "@babel/register": "~7.5.5",
61 | "@trendmicro/babel-config": "~1.0.0-alpha",
62 | "babel-eslint": "~10.0.2",
63 | "babel-loader": "~8.0.6",
64 | "classnames": "~2.2.6",
65 | "coveralls": "~3.0.5",
66 | "cross-env": "~5.2.0",
67 | "css-loader": "~3.1.0",
68 | "enzyme": "~3.10.0",
69 | "enzyme-adapter-react-16": "~1.14.0",
70 | "eslint": "~6.0.1",
71 | "eslint-config-trendmicro": "~1.4.1",
72 | "eslint-loader": "~2.2.1",
73 | "eslint-plugin-import": "~2.18.1",
74 | "eslint-plugin-jsx-a11y": "~6.2.3",
75 | "eslint-plugin-react": "~7.14.2",
76 | "find-imports": "~1.1.0",
77 | "html-webpack-plugin": "~3.2.0",
78 | "http-server": "~0.11.1",
79 | "jsdom": "~15.1.1",
80 | "rc-slider": "~8.6.13",
81 | "react": "~16.8.0",
82 | "react-bootstrap-buttons": "~0.5.0",
83 | "react-dom": "~16.8.0",
84 | "react-github-corner": "~2.3.0",
85 | "react-styleguidist": "~9.1.11",
86 | "sinon": "~7.3.2",
87 | "style-loader": "~0.23.1",
88 | "styled-components": "~4.3.2",
89 | "tap": "~14.4.2",
90 | "webpack": "~4.36.1",
91 | "webpack-cli": "~3.3.6",
92 | "webpack-dev-server": "~3.7.2"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/setupTests.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 | import { JSDOM } from 'jsdom';
4 |
5 | // React 16 Enzyme adapter
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
8 | // Ignore `.styl` files
9 | require.extensions['.styl'] = () => {
10 | return;
11 | };
12 |
13 | // JSDOM
14 | const jsdom = new JSDOM('');
15 | const { window } = jsdom;
16 |
17 | const copyProps = (src, target) => {
18 | const props = Object.getOwnPropertyNames(src)
19 | .filter(prop => typeof target[prop] === 'undefined')
20 | .reduce((result, prop) => ({
21 | ...result,
22 | [prop]: Object.getOwnPropertyDescriptor(src, prop),
23 | }), {});
24 | Object.defineProperties(target, props);
25 | };
26 |
27 | global.window = window;
28 | global.document = window.document;
29 | global.navigator = {
30 | userAgent: 'node.js',
31 | };
32 |
33 | copyProps(window, global);
34 |
--------------------------------------------------------------------------------
/src/Repeatable.jsx:
--------------------------------------------------------------------------------
1 | import chainedFunction from 'chained-function';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | class Repeatable extends React.Component {
6 | static propTypes = {
7 | // A custom element for this component.
8 | tag: PropTypes.oneOfType([
9 | PropTypes.func,
10 | PropTypes.string,
11 | PropTypes.shape({ $$typeof: PropTypes.symbol, render: PropTypes.func }),
12 | PropTypes.arrayOf(PropTypes.oneOfType([
13 | PropTypes.func,
14 | PropTypes.string,
15 | PropTypes.shape({ $$typeof: PropTypes.symbol, render: PropTypes.func }),
16 | ]))
17 | ]),
18 |
19 | // Set it to true to disable event actions.
20 | disabled: PropTypes.bool,
21 |
22 | // The time (in milliseconds) to wait before the first hold action is being triggered.
23 | repeatDelay: PropTypes.oneOfType([
24 | PropTypes.number,
25 | PropTypes.string
26 | ]),
27 |
28 | // The time interval (in milliseconds) on how often to trigger a hold action.
29 | repeatInterval: PropTypes.oneOfType([
30 | PropTypes.number,
31 | PropTypes.string
32 | ]),
33 |
34 | // The number of times the hold action will take place. A zero value will disable the repeat counter.
35 | repeatCount: PropTypes.oneOfType([
36 | PropTypes.number,
37 | PropTypes.string
38 | ]),
39 |
40 | // Callback fired when the mousedown or touchstart event is triggered.
41 | onPress: PropTypes.func,
42 |
43 | // Callback fired once before the first hold action.
44 | onHoldStart: PropTypes.func,
45 |
46 | // Callback fired mutiple times while holding down.
47 | onHold: PropTypes.func,
48 |
49 | // Callback fired once after the last hold action.
50 | onHoldEnd: PropTypes.func,
51 |
52 | // Callback fired when the mouseup, touchcancel, or touchend event is triggered.
53 | onRelease: PropTypes.func,
54 |
55 | onMouseDown: PropTypes.func,
56 | onTouchStart: PropTypes.func,
57 | onTouchCancel: PropTypes.func,
58 | onTouchEnd: PropTypes.func
59 | };
60 |
61 | static defaultProps = {
62 | tag: 'div',
63 | disabled: false,
64 | repeatDelay: 500,
65 | repeatInterval: 32,
66 | repeatCount: 0
67 | };
68 |
69 | repeatDelayTimer = null;
70 |
71 | repeatIntervalTimer = null;
72 |
73 | repeatAmount = 0;
74 |
75 | acquireTimer = () => {
76 | const repeatDelay = Math.max(Number(this.props.repeatDelay) || 0, 0);
77 | const repeatInterval = Math.max(Number(this.props.repeatInterval) || 0, 0);
78 | const repeatCount = Math.max(Number(this.props.repeatCount) || 0, 0);
79 |
80 | this.repeatAmount = 0;
81 | this.releaseTimer();
82 |
83 | this.repeatDelayTimer = setTimeout(() => {
84 | if ((repeatCount > 0) && (this.repeatAmount >= repeatCount)) {
85 | return;
86 | }
87 |
88 | this.repeatAmount++;
89 |
90 | if (typeof this.props.onHoldStart === 'function') {
91 | this.props.onHoldStart();
92 | }
93 | if (typeof this.props.onHold === 'function') {
94 | this.props.onHold();
95 | }
96 |
97 | this.repeatIntervalTimer = setInterval(() => {
98 | if ((repeatCount > 0) && (this.repeatAmount >= repeatCount)) {
99 | return;
100 | }
101 |
102 | this.repeatAmount++;
103 |
104 | if (typeof this.props.onHold === 'function') {
105 | this.props.onHold();
106 | }
107 | }, repeatInterval);
108 | }, repeatDelay);
109 | };
110 |
111 | releaseTimer = () => {
112 | if (this.repeatDelayTimer) {
113 | clearTimeout(this.repeatDelayTimer);
114 | this.repeatDelayTimer = null;
115 | }
116 | if (this.repeatIntervalTimer) {
117 | clearInterval(this.repeatIntervalTimer);
118 | this.repeatIntervalTimer = null;
119 | }
120 | };
121 |
122 | handleRelease = (event) => {
123 | if (this.props.disabled) {
124 | return;
125 | }
126 |
127 | if (this.repeatAmount > 0) {
128 | if (typeof this.props.onHoldEnd === 'function') {
129 | this.props.onHoldEnd();
130 | }
131 | }
132 |
133 | this.repeatAmount = 0;
134 | this.releaseTimer();
135 |
136 | if (typeof this.props.onRelease === 'function') {
137 | this.props.onRelease(event);
138 | }
139 | };
140 |
141 | handlePress = (event) => {
142 | if (this.props.disabled) {
143 | return;
144 | }
145 |
146 | event.persist();
147 |
148 | const releaseOnce = (event) => {
149 | document.documentElement.removeEventListener('mouseup', releaseOnce);
150 | this.handleRelease(event);
151 | };
152 | document.documentElement.addEventListener('mouseup', releaseOnce);
153 |
154 | if (typeof this.props.onPress === 'function') {
155 | this.props.onPress(event);
156 | }
157 |
158 | this.acquireTimer();
159 | };
160 |
161 | componentWillUnmount() {
162 | this.repeatAmount = 0;
163 | this.releaseTimer();
164 | }
165 |
166 | render() {
167 | const {
168 | tag: Tag,
169 | repeatDelay, // eslint-disable-line
170 | repeatInterval, // eslint-disable-line
171 | repeatCount, // eslint-disable-line
172 | onPress, // eslint-disable-line
173 | onHoldStart, // eslint-disable-line
174 | onHold, // eslint-disable-line
175 | onHoldEnd, // eslint-disable-line
176 | onRelease, // eslint-disable-line
177 | onMouseDown,
178 | onTouchStart,
179 | onTouchCancel,
180 | onTouchEnd,
181 | ...props
182 | } = this.props;
183 |
184 | return (
185 |
205 | );
206 | }
207 | }
208 |
209 | export default Repeatable;
210 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Repeatable from './Repeatable';
2 |
3 | export default Repeatable;
4 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const pkg = require('./package.json');
4 |
5 | const webpackConfig = {
6 | mode: 'development',
7 | devtool: 'cheap-module-eval-source-map',
8 | devServer: {
9 | disableHostCheck: true,
10 | contentBase: path.resolve(__dirname, 'docs'),
11 | },
12 | entry: path.resolve(__dirname, 'src/index.js'),
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx?$/,
17 | loader: 'eslint-loader',
18 | enforce: 'pre',
19 | exclude: /node_modules/
20 | },
21 | {
22 | test: /\.jsx?$/,
23 | loader: 'babel-loader',
24 | exclude: /node_modules/
25 | },
26 | {
27 | test: /\.css$/,
28 | use: [
29 | 'style-loader',
30 | 'css-loader'
31 | ]
32 | },
33 | ]
34 | },
35 | plugins: [
36 | new webpack.DefinePlugin({
37 | 'process.env': {
38 | // This has effect on the react lib size
39 | NODE_ENV: JSON.stringify('production')
40 | }
41 | }),
42 | ],
43 | resolve: {
44 | extensions: ['.js', '.json', '.jsx']
45 | }
46 | };
47 |
48 | module.exports = {
49 | title: `React Component v${pkg.version}`,
50 | sections: [
51 | {
52 | name: 'Repeatable',
53 | content: path.resolve(__dirname, 'styleguide/examples/README.md'),
54 | }
55 | ],
56 | require: [
57 | '@babel/polyfill',
58 | path.resolve(__dirname, 'styleguide/setup.js'),
59 | path.resolve(__dirname, 'styleguide/styles.css'),
60 | ],
61 | ribbon: {
62 | url: pkg.homepage,
63 | text: 'Fork me on GitHub'
64 | },
65 | serverPort: 8080,
66 | exampleMode: 'collapse',
67 | usageMode: 'expand',
68 | showSidebar: true,
69 | styleguideComponents: {
70 | StyleGuideRenderer: path.join(__dirname, 'styleguide/components/StyleGuideRenderer.jsx'),
71 | Wrapper: path.join(__dirname, 'styleguide/components/Wrapper.jsx'),
72 | },
73 | styleguideDir: 'docs/',
74 | webpackConfig: webpackConfig
75 | };
76 |
--------------------------------------------------------------------------------
/styleguide/components/Progress.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | const Progress = ({ min = 0, max = 100, now = 0, style, ...props }) => (
5 |
17 |
31 | {now}%
32 |
33 |
34 | );
35 |
36 | Progress.propTypes = {
37 | min: PropTypes.number,
38 | max: PropTypes.number,
39 | now: PropTypes.number
40 | };
41 |
42 | export default Progress;
43 |
--------------------------------------------------------------------------------
/styleguide/components/StyleGuideRenderer.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import GitHubCorner from 'react-github-corner';
4 | import styled from 'styled-components';
5 | import pkg from '../../package.json';
6 |
7 | const Root = styled.div`
8 | min-height: 100vh;
9 | background-color: #fff;
10 | padding-left: 240px;
11 | `;
12 |
13 | const Main = styled.main`
14 | padding: 16px 32px;
15 | margin: 0 auto;
16 | display: block;
17 | `;
18 |
19 | const Sidebar = styled.div`
20 | background-color: #f5f5f5;
21 | border: #e8e8e8 solid;
22 | border-width: 0 1px 0 0;
23 | position: fixed;
24 | top: 0;
25 | left: 0;
26 | bottom: 0;
27 | width: 240px;
28 | overflow: auto;
29 | -webkit-overflow-scrolling: touch;
30 | `;
31 |
32 | const Block = styled.div``;
33 |
34 | const TextBlock = styled(Block)`
35 | padding: 16px;
36 | border-bottom: 1px #e8e8e8 solid;
37 | color: #333;
38 | margin: 0;
39 | font-size: 18px;
40 | font-weight: normal;
41 | `;
42 |
43 | const StyleGuideRenderer = ({
44 | title,
45 | toc,
46 | children,
47 | }) => {
48 | return (
49 |
50 |
51 |
52 |
53 | {title}
54 |
55 |
56 | {toc}
57 |
58 |
59 |
60 | {children}
61 |
62 |
63 | );
64 | };
65 |
66 | StyleGuideRenderer.propTypes = {
67 | title: PropTypes.string,
68 | toc: PropTypes.node,
69 | };
70 |
71 | export default StyleGuideRenderer;
72 |
--------------------------------------------------------------------------------
/styleguide/components/Wrapper.jsx:
--------------------------------------------------------------------------------
1 | const Wrapper = ({ children }) => {
2 | return children;
3 | };
4 |
5 | export default Wrapper;
6 |
--------------------------------------------------------------------------------
/styleguide/examples/README.md:
--------------------------------------------------------------------------------
1 | ```jsx
2 | import 'react-bootstrap-buttons/dist/react-bootstrap-buttons.css';
3 | import 'rc-slider/dist/rc-slider.css';
4 | import cx from 'classnames';
5 | import { Button } from 'react-bootstrap-buttons';
6 | import Slider from 'rc-slider';
7 | import Progress from '../components/Progress';
8 |
9 | initialState = {
10 | repeatDelay: Repeatable.defaultProps.repeatDelay,
11 | repeatInterval: Repeatable.defaultProps.repeatInterval,
12 | repeatCount: 0,
13 | button1: {
14 | pressed: false,
15 | holding: false
16 | },
17 | button2: {
18 | pressed: false,
19 | holding: false
20 | },
21 | value: 0
22 | };
23 |
24 | const RepeatableButton = ({ onClick, ...props }) => (
25 |
31 | );
32 |
33 |
34 |
35 |
38 |
39 | {
45 | setState({ repeatDelay: value });
46 | }}
47 | />
48 |
49 |
50 |
51 |
54 |
55 | {
61 | setState({ repeatInterval: value });
62 | }}
63 | />
64 |
65 |
66 |
67 |
70 |
71 | {
77 | setState({ repeatCount: value });
78 | }}
79 | />
80 |
81 |
82 |
90 |
{
101 | console.log('[1] onPress');
102 | setState({
103 | button1: {
104 | pressed: true,
105 | holding: false
106 | }
107 | });
108 | }}
109 | onHoldStart={() => {
110 | console.log('[1] onHoldStart');
111 | setState({
112 | value: 0
113 | });
114 | }}
115 | onHold={() => {
116 | console.log('[1] onHold');
117 | setState(state => ({
118 | button1: {
119 | pressed: true,
120 | holding: true
121 | },
122 | value: Math.min(state.value + 1, 100)
123 | }));
124 | }}
125 | onHoldEnd={() => {
126 | console.log('[1] onHoldEnd');
127 | setState({
128 | value: 0
129 | });
130 | }}
131 | onRelease={() => {
132 | console.log('[1] onRelease');
133 | setState({
134 | button1: {
135 | pressed: false,
136 | holding: false
137 | }
138 | });
139 | }}
140 | >
141 | {!state.button1.pressed && 'Press Me (1x)'}
142 | {state.button1.pressed && !state.button1.holding && 'Pressing... (1x)'}
143 | {state.button1.pressed && state.button1.holding && 'Holding (1x)'}
144 |
145 |
{
156 | console.log('[2] onPress');
157 | setState({
158 | button2: {
159 | pressed: true,
160 | holding: false
161 | }
162 | });
163 | }}
164 | onHoldStart={() => {
165 | console.log('[2] onHoldStart');
166 | setState({
167 | value: 0
168 | });
169 | }}
170 | onHold={() => {
171 | console.log('[2] onHold');
172 | setState(state => ({
173 | button2: {
174 | pressed: true,
175 | holding: true
176 | },
177 | value: Math.min(state.value + 1, 100)
178 | }));
179 | }}
180 | onHoldEnd={() => {
181 | console.log('[2] onHoldEnd');
182 | setState({
183 | value: 0
184 | });
185 | }}
186 | onRelease={(event) => {
187 | console.log('[2] onRelease');
188 | setState({
189 | button2: {
190 | pressed: false,
191 | holding: false
192 | }
193 | });
194 | }}
195 | >
196 | {!state.button2.pressed && 'Press Me (5x)'}
197 | {state.button2.pressed && !state.button2.holding && 'Pressing... (5x)'}
198 | {state.button2.pressed && state.button2.holding && 'Holding (5x)'}
199 |
200 |
201 | ```
202 |
--------------------------------------------------------------------------------
/styleguide/setup.js:
--------------------------------------------------------------------------------
1 | import Repeatable from '../src/Repeatable';
2 |
3 | global.Repeatable = Repeatable;
4 |
--------------------------------------------------------------------------------
/styleguide/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
3 | }
4 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'enzyme';
3 | import sinon from 'sinon';
4 | import { test } from 'tap';
5 | import '../setupTests';
6 | import Repeatable from '../src';
7 |
8 | test('', (t) => {
9 | const wrapper = mount((
10 |
11 |
12 |
13 | ));
14 | t.equal(wrapper.find(Repeatable).length, 1, 'should render component');
15 | t.end();
16 | });
17 |
18 | test('simulates click event', (t) => {
19 | const onClick = sinon.spy();
20 | const wrapper = mount();
21 | wrapper.find(Repeatable).simulate('click');
22 | t.ok(onClick.calledOnce, 'should be called once');
23 | t.end();
24 | });
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const findImports = require('find-imports');
3 | const webpack = require('webpack');
4 | const pkg = require('./package.json');
5 | const babelConfig = require('./babel.config');
6 |
7 | module.exports = {
8 | mode: 'development',
9 | devtool: 'source-map',
10 | entry: {
11 | [pkg.name]: path.resolve(__dirname, 'src/index.js')
12 | },
13 | output: {
14 | path: path.join(__dirname, 'lib'),
15 | filename: 'index.js',
16 | libraryTarget: 'commonjs2'
17 | },
18 | externals: []
19 | .concat(findImports(['src/**/*.{js,jsx}'], { flatten: true }))
20 | .concat(Object.keys(pkg.peerDependencies))
21 | .concat(Object.keys(pkg.dependencies)),
22 | module: {
23 | rules: [
24 | {
25 | test: /\.jsx?$/,
26 | loader: 'eslint-loader',
27 | enforce: 'pre',
28 | exclude: /node_modules/
29 | },
30 | {
31 | test: /\.jsx?$/,
32 | loader: 'babel-loader',
33 | exclude: /node_modules/,
34 | options: babelConfig
35 | },
36 | ]
37 | },
38 | plugins: [
39 | new webpack.DefinePlugin({
40 | 'process.env': {
41 | // This has effect on the react lib size
42 | NODE_ENV: JSON.stringify('production')
43 | }
44 | }),
45 | ],
46 | resolve: {
47 | extensions: ['.js', '.json', '.jsx']
48 | }
49 | };
50 |
--------------------------------------------------------------------------------