├── .travis.yml ├── src ├── Util │ └── Confetti.js ├── LuckyDraw │ ├── index.js │ ├── Arc.js │ ├── Wheel.js │ ├── Style │ │ └── LuckyDraw.styl │ └── LuckyDraw.js ├── App.test.js ├── index.js ├── App.styl ├── btn.css └── App.js ├── .npmignore ├── public ├── favicon.ico ├── manifest.webmanifest └── index.html ├── .gitignore ├── sw-precache-config.js ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── env.js ├── paths.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── lib ├── index.js ├── LuckyDraw.css ├── Arc.js ├── Wheel.js └── LuckyDraw.js ├── scripts ├── test.js ├── build.js └── start.js ├── README.md └── package.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" -------------------------------------------------------------------------------- /src/Util/Confetti.js: -------------------------------------------------------------------------------- 1 | export default class Confetti {} 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | src 4 | coverage 5 | test 6 | example 7 | karma.conf.js -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gemerz/react-luckydraw/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/LuckyDraw/index.js: -------------------------------------------------------------------------------- 1 | import LuckyDraw from './LuckyDraw'; 2 | 3 | export default LuckyDraw; 4 | -------------------------------------------------------------------------------- /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 | }); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | .idea 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | npm-debug.log 17 | -------------------------------------------------------------------------------- /sw-precache-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stripPrefix: 'build/', 3 | staticFileGlobs: [ 4 | 'build/*.html', 5 | 'build/manifest.json', 6 | 'build/static/**/!(*map*)' 7 | ], 8 | dontCacheBustUrlsMatching: /\.\w{8}\./, 9 | swFilePath: 'build/service-worker.js' 10 | }; 11 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _LuckyDraw = require('./LuckyDraw'); 8 | 9 | var _LuckyDraw2 = _interopRequireDefault(_LuckyDraw); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = _LuckyDraw2.default; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | 7 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 8 | window.addEventListener('load', function() { 9 | navigator.serviceWorker.register('./service-worker.js'); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};'; 7 | }, 8 | getCacheKey(fileData, filename) { 9 | // The output is always the same. 10 | return 'cssTransform'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/App.styl: -------------------------------------------------------------------------------- 1 | body 2 | width 100% 3 | overflow hidden 4 | margin 0 5 | padding 0 6 | color #ffffff 7 | background-color darken(#252B37, 3%); 8 | font-family -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif 9 | 10 | .App 11 | max-width 920px 12 | margin 0 auto 13 | min-height 100vh 14 | display flex 15 | flex-direction column 16 | justify-content center 17 | align-items center 18 | 19 | h1 20 | color #ffffff 21 | padding 0px 0 30px 0 22 | 23 | .luckdraw 24 | width 550px 25 | height 350px 26 | 27 | .ReactTabs__TabPanel>div 28 | margin 0 auto 29 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-luckydraw", 3 | "short_name": "react-luckydraw", 4 | "description": "Progressive Web react-luckydraw component", 5 | "start_url": "./?app=true", 6 | "display": "fullscreen", 7 | "orientation": "portrait", 8 | "background_color": "#fff", 9 | "theme_color": "#34495f", 10 | "icons": [{ 11 | "src": "./favicon-16x16.png", 12 | "sizes": "16x16", 13 | "type": "image/png" 14 | }, { 15 | "src": "./favicon-32x32.png", 16 | "sizes": "32x32", 17 | "type": "image/png" 18 | }, { 19 | "src": "./android-chrome-192x192.png", 20 | "sizes": "192x192", 21 | "type": "image/png" 22 | }, { 23 | "src": "./android-chrome-256x256.png", 24 | "sizes": "256x256", 25 | "type": "image/png" 26 | }] 27 | } 28 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | process.env.PUBLIC_URL = ''; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | const jest = require('jest'); 11 | const argv = process.argv.slice(2); 12 | 13 | // Watch unless on CI or in coverage mode 14 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 15 | argv.push('--watch'); 16 | } 17 | 18 | // A temporary hack to clear terminal correctly. 19 | // You can remove this after updating to Jest 18 when it's out. 20 | // https://github.com/facebook/jest/pull/2230 21 | var realWrite = process.stdout.write; 22 | var CLEAR = process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'; 23 | process.stdout.write = function(chunk, encoding, callback) { 24 | if (chunk === '\x1B[2J\x1B[H') { 25 | chunk = CLEAR; 26 | } 27 | return realWrite.call(this, chunk, encoding, callback); 28 | }; 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | React LuckyDraw 17 | 18 | 19 | Fork me on GitHub 23 |
24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 23 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 24 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 25 | 26 | var nodePaths = (process.env.NODE_PATH || '') 27 | .split(process.platform === 'win32' ? ';' : ':') 28 | .filter(Boolean) 29 | .filter(folder => !path.isAbsolute(folder)) 30 | .map(resolveApp); 31 | 32 | // config after eject: we're in ./config/ 33 | module.exports = { 34 | appBuild: resolveApp('build'), 35 | appPublic: resolveApp('public'), 36 | appHtml: resolveApp('public/index.html'), 37 | appIndexJs: resolveApp('src/index.js'), 38 | appPackageJson: resolveApp('package.json'), 39 | appSrc: resolveApp('src'), 40 | yarnLockFile: resolveApp('yarn.lock'), 41 | testsSetup: resolveApp('src/setupTests.js'), 42 | appNodeModules: resolveApp('node_modules'), 43 | ownNodeModules: resolveApp('node_modules'), 44 | nodePaths: nodePaths, 45 | appComponent: resolveApp('dist'), 46 | appComponentIndexJs: resolveApp('src/LuckyDraw/index.js'), 47 | }; 48 | -------------------------------------------------------------------------------- /src/LuckyDraw/Arc.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { arc } from 'd3'; 3 | 4 | class Arc extends Component { 5 | static propTypes = { 6 | text: React.PropTypes.oneOfType([ 7 | React.PropTypes.string, 8 | React.PropTypes.number 9 | ]), 10 | startAngle: React.PropTypes.number, 11 | endAngle: React.PropTypes.number, 12 | innerRadius: React.PropTypes.number, 13 | outerRadius: React.PropTypes.number, 14 | labelTextRotate: React.PropTypes.number, 15 | stoke: React.PropTypes.number, 16 | showInnerLabels: React.PropTypes.bool, 17 | fontColor: React.PropTypes.string, 18 | fontSize: React.PropTypes.string, 19 | fontFamily: React.PropTypes.string, 20 | writingModel: React.PropTypes.string 21 | }; 22 | 23 | renderInnerLabel(props, Arc) { 24 | let midAngle = props.endAngle < Math.PI 25 | ? props.startAngle / 2 + props.endAngle / 2 26 | : props.startAngle / 2 + props.endAngle / 2 + Math.PI; 27 | let textAngle = midAngle * 180 / Math.PI > 180 28 | ? midAngle * 180 / Math.PI - 180 29 | : midAngle * 180 / Math.PI; 30 | return ( 31 | 43 | {props.text} 44 | 45 | ); 46 | } 47 | 48 | render() { 49 | const props = this.props; 50 | const Arc = arc() 51 | .innerRadius(props.innerRadius) 52 | .outerRadius(props.outerRadius) 53 | .startAngle(props.startAngle) 54 | .endAngle(props.endAngle); 55 | 56 | return ( 57 | 58 | 59 | {props.showInnerLabels ? this.renderInnerLabel(props, Arc) : null} 60 | 61 | ); 62 | } 63 | } 64 | 65 | export default Arc; 66 | -------------------------------------------------------------------------------- /src/LuckyDraw/Wheel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { schemeCategory20, pie } from 'd3'; 3 | import Arc from './Arc'; 4 | 5 | class Wheel extends Component { 6 | static propTypes = { 7 | wheelSize: React.PropTypes.number.isRequired, 8 | range: React.PropTypes.number.isRequired, 9 | innerRadius: React.PropTypes.number, 10 | outerRadius: React.PropTypes.number, 11 | stoke: React.PropTypes.number, 12 | showInnerLabels: React.PropTypes.bool, 13 | textArray: React.PropTypes.array, 14 | fontColor: React.PropTypes.string, 15 | fontSize: React.PropTypes.string, 16 | writingModel: React.PropTypes.string 17 | }; 18 | static defaultProps = {}; 19 | 20 | _processData(range) { 21 | let array = []; 22 | for (var i = 0; i < range; i++) { 23 | array.push(100 / range); 24 | } 25 | return array; 26 | } 27 | 28 | render() { 29 | const props = this.props; 30 | const transform = `translate(${props.wheelSize / 2},${props.wheelSize / 2}) rotate(-${180 / props.range})`; 31 | const data = this._processData(props.range); 32 | const arcs = pie()(data).sort(); 33 | const Pie = arcs.map((i, idx) => { 34 | let colorIdx = idx > 19 ? idx % 20 : idx; 35 | const textLabel = !props.ArabicLabel 36 | ? props.textArray[idx] ? props.textArray[idx] : idx + 1 37 | : idx + 1; 38 | 39 | return ( 40 | 54 | ); 55 | }); 56 | return ( 57 | 58 | 59 | {Pie} 60 | 61 | 62 | ); 63 | } 64 | } 65 | 66 | export default Wheel; 67 | -------------------------------------------------------------------------------- /src/LuckyDraw/Style/LuckyDraw.styl: -------------------------------------------------------------------------------- 1 | backlight($x, $y, $spread, $size, $colorA, $colorB, $duration) 2 | &:after 3 | position absolute 4 | content "" 5 | top $y 6 | left $x 7 | right 0 8 | z-index 1 9 | height 100% 10 | width 100% 11 | margin 0 auto 12 | transform scale($size) 13 | filter blur($spread) 14 | background linear-gradient(270deg, $colorA, $colorB) 15 | background-size 200% 200% 16 | animation animateGlow $duration ease infinite 17 | 18 | .compass__container 19 | width 100% 20 | height 100% 21 | display inline-block 22 | animation textColor 10s ease infinite 23 | position relative 24 | box-shadow 0px 10px 50px rgba(0, 0, 0, 0.85) 25 | backlight 0, 5vw, 5vw, 0.85, #0fffc1, #7e0fff, 5s 26 | &.rolling:after 27 | animation none 28 | 29 | .compass__arrow 30 | position absolute 31 | height 50px 32 | width 100% 33 | top 0 34 | z-index 10 35 | text-align center 36 | &:before 37 | content: ""; 38 | top 200% 39 | width 0 40 | height: 0 41 | border-left 20px solid transparent 42 | border-right 20px solid transparent 43 | border-top 100px solid lighten(darkred, 40%); 44 | position relative 45 | z-index 10 46 | 47 | //&:before 48 | 49 | .control__panel 50 | position relative 51 | height calc(100% - 74px) 52 | width calc(100% - 24px) 53 | margin 0 auto 54 | padding-top 50px 55 | overflow hidden 56 | border 12px solid darkorange 57 | display: flex 58 | flex-wrap nowrap 59 | flex-direction column 60 | align-items center 61 | justify-content flex-start 62 | background-color darken(#fff, 4%); 63 | z-index 2 64 | box-shadow inset 0px 2px 40px rgba(0, 0, 0, 0.45); 65 | 66 | .compass__spin 67 | width 100% 68 | height 100% 69 | z-index 2 70 | transition-property transform 71 | transition-timing-function ease-out 72 | animation-fill-mode forwards 73 | 74 | .compass__btn 75 | position absolute 76 | text-align center 77 | bottom -50px 78 | left 0 79 | z-index 100 80 | display inline-block 81 | width 100% 82 | .bttn-jelly.bttn-danger 83 | font-size 16px 84 | padding 10px 20px 85 | 86 | @keyframes animateGlow 87 | 0% 88 | background-position: 0% 50% 89 | 90 | 50% 91 | background-position: 100% 50% 92 | 100% 93 | background-position: 0% 50% 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #react-luckydraw 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/Gemerz/react-luckydraw.svg)](https://greenkeeper.io/) 4 | ![](https://travis-ci.org/Gemerz/react-luckydraw.svg?branch=master) 5 | 6 | 7 | 8 | ## Installation 9 | 10 | ```js 11 | $ npm install --save react-luckydraw 12 | ``` 13 | 14 | ## Usage 15 | ``` javascript 16 | import React from 'react'; 17 | import LuckyDraw from 'react-luckydraw'; 18 | 19 | // include styles 20 | import 'react-luckydraw/lib/LuckyDraw.css'; 21 | 22 | class App extends React.Component { 23 | 24 | constructor(props) { 25 | super(props); 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | { 49 | console.log(drawNumber) 50 | }} 51 | onOutLimitAlert={(limit) => { 52 | if (limit) { 53 | window.alert('out of limits') 54 | } 55 | }} 56 | /> 57 |
58 | ) 59 | } 60 | } 61 | ``` 62 | 63 | ## Props 64 | 65 | Property|Type|Default|Description 66 | ---|---|---|--- 67 | width|number|500|width of luckydraw 68 | height|number|350|height of luckydraw 69 | wheelSize|number|width * 2|size of luckydraw Wheel 70 | range|number|30|range of luckydraw 71 | outerRadius|number|wheelSize/2|outerRadius of luckydraw Wheel 72 | innerRadius|number|outerRadius/2|innerRadius of luckydraw Wheel 73 | turns|number|3| Defines how many revolutions to rotate 74 | rotateSecond|number|5| Define how many seconds to turn around 75 | showInnerLabels|boolean|true|show Labels on luckydraw Wheel 76 | drawLimitSwitch|boolean|true|a switch of drawing wheel limit 77 | drawLimit|number|5|a limit for drawing times with wheel 78 | fontColor|string|'black'|set color for label on wheel 79 | fontSize|string|'18px'|set size for label on wheel 80 | drawButtonLabel|string|'start'|set text for drawing button 81 | textArray|array| - |set text array for wheel label 82 | onSuccessDrawReturn|function| - |callback function for draw success with the draw number 83 | onOutLimitAlert|function| - |callback function for limit times out 84 | 85 | 86 | -------------------------------------------------------------------------------- /lib/LuckyDraw.css: -------------------------------------------------------------------------------- 1 | .compass__container { 2 | width: 100%; 3 | height: 100%; 4 | display: inline-block; 5 | animation: textColor 10s ease infinite; 6 | position: relative; 7 | box-shadow: 0px 10px 50px rgba(0,0,0,0.85); 8 | } 9 | .compass__container:after { 10 | position: absolute; 11 | content: ""; 12 | top: 5vw; 13 | left: 0; 14 | right: 0; 15 | z-index: 1; 16 | height: 100%; 17 | width: 100%; 18 | margin: 0 auto; 19 | transform: scale(0.85); 20 | filter: blur(5vw); 21 | background: linear-gradient(270deg, #0fffc1, #7e0fff); 22 | background-size: 200% 200%; 23 | animation: animateGlow 5s ease infinite; 24 | } 25 | .compass__container.rolling:after { 26 | animation: none; 27 | } 28 | .compass__arrow { 29 | position: absolute; 30 | height: 50px; 31 | width: 100%; 32 | top: 0; 33 | left:0; 34 | z-index: 10; 35 | text-align: center; 36 | } 37 | .compass__arrow:before { 38 | content: ""; 39 | top: 200%; 40 | width: 0; 41 | height: 0; 42 | border-left: 20px solid transparent; 43 | border-right: 20px solid transparent; 44 | border-top: 100px solid #ff2020; 45 | position: relative; 46 | z-index: 10; 47 | } 48 | .control__panel { 49 | position: relative; 50 | height: calc(100% - 74px); 51 | width: calc(100% - 24px); 52 | margin: 0 auto; 53 | padding-top: 50px; 54 | overflow: hidden; 55 | border: 12px solid #ff8c00; 56 | display: flex; 57 | flex-wrap: nowrap; 58 | flex-direction: column; 59 | align-items: center; 60 | justify-content: flex-start; 61 | background-color: #f5f5f5; 62 | z-index: 2; 63 | box-shadow: inset 0px 2px 40px rgba(0,0,0,0.45); 64 | } 65 | .compass__spin { 66 | width: 100%; 67 | height: 100%; 68 | z-index: 2; 69 | transition-property: transform; 70 | transition-timing-function: ease-out; 71 | animation-fill-mode: forwards; 72 | } 73 | .compass__btn { 74 | position: absolute; 75 | text-align: center; 76 | bottom: -50px; 77 | left: 0; 78 | z-index: 100; 79 | display: inline-block; 80 | width: 100%; 81 | } 82 | .compass__btn .bttn-jelly.bttn-danger { 83 | font-size: 16px; 84 | padding: 10px 20px; 85 | } 86 | @-moz-keyframes animateGlow { 87 | 0% { 88 | background-position: 0% 50%; 89 | } 90 | 50% { 91 | background-position: 100% 50%; 92 | } 93 | 100% { 94 | background-position: 0% 50%; 95 | } 96 | } 97 | @-webkit-keyframes animateGlow { 98 | 0% { 99 | background-position: 0% 50%; 100 | } 101 | 50% { 102 | background-position: 100% 50%; 103 | } 104 | 100% { 105 | background-position: 0% 50%; 106 | } 107 | } 108 | @-o-keyframes animateGlow { 109 | 0% { 110 | background-position: 0% 50%; 111 | } 112 | 50% { 113 | background-position: 100% 50%; 114 | } 115 | 100% { 116 | background-position: 0% 50%; 117 | } 118 | } 119 | @keyframes animateGlow { 120 | 0% { 121 | background-position: 0% 50%; 122 | } 123 | 50% { 124 | background-position: 100% 50%; 125 | } 126 | 100% { 127 | background-position: 0% 50%; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-luckydraw", 3 | "version": "0.1.6", 4 | "author": "Gemer Cheung ", 5 | "description": "Simple luckydraw component for React.", 6 | "main": "lib/index.js", 7 | "homepage": "http://blog.gemer.xyz/react-luckydraw", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/Gemerz/react-luckydraw.git" 11 | }, 12 | "keywords": [ 13 | "react", 14 | "luckydraw" 15 | ], 16 | "license": "ISC", 17 | "prepublish": "npm run build-lib && npm run build-css", 18 | "devDependencies": { 19 | "autoprefixer": "7.1.6", 20 | "babel-core": "6.26.0", 21 | "babel-eslint": "8.0.2", 22 | "babel-jest": "21.2.0", 23 | "babel-loader": "7.1.2", 24 | "babel-preset-react-app": "^3.1.0", 25 | "case-sensitive-paths-webpack-plugin": "2.1.1", 26 | "chalk": "2.3.0", 27 | "connect-history-api-fallback": "1.5.0", 28 | "cross-spawn": "5.1.0", 29 | "css-loader": "0.28.7", 30 | "detect-port": "1.2.2", 31 | "dotenv": "4.0.0", 32 | "eslint": "4.11.0", 33 | "eslint-config-react-app": "^2.0.1", 34 | "eslint-loader": "1.9.0", 35 | "eslint-plugin-flowtype": "2.39.1", 36 | "eslint-plugin-import": "2.8.0", 37 | "eslint-plugin-jsx-a11y": "6.0.2", 38 | "eslint-plugin-react": "7.4.0", 39 | "extract-text-webpack-plugin": "3.0.2", 40 | "file-loader": "1.1.5", 41 | "filesize": "3.5.11", 42 | "fs-extra": "4.0.2", 43 | "gh-pages": "^1.1.0", 44 | "gzip-size": "4.0.0", 45 | "highlight.js": "^9.9.0", 46 | "html-webpack-plugin": "2.30.1", 47 | "http-proxy-middleware": "0.17.4", 48 | "jest": "21.2.1", 49 | "json-loader": "0.5.7", 50 | "object-assign": "4.1.1", 51 | "path-exists": "3.0.0", 52 | "postcss-loader": "2.0.8", 53 | "prettier": "^1.8.2", 54 | "promise": "8.0.1", 55 | "react-dev-utils": "^4.2.1", 56 | "react-highlight": "^0.10.0", 57 | "recursive-readdir": "2.2.1", 58 | "strip-ansi": "4.0.0", 59 | "style-loader": "0.19.0", 60 | "stylus": "^0.54.5", 61 | "stylus-loader": "^3.0.1", 62 | "sw-precache": "^5.0.0", 63 | "url-loader": "0.6.2", 64 | "webpack": "3.8.1", 65 | "webpack-dev-server": "2.9.4", 66 | "webpack-manifest-plugin": "1.3.2", 67 | "whatwg-fetch": "2.0.3" 68 | }, 69 | "dependencies": { 70 | "d3": "^4.4.0", 71 | "react": "^16.1.1", 72 | "react-dom": "^16.1.1", 73 | "react-tabs": "^2.1.1", 74 | "styled-components": "^2.2.3", 75 | "sweetalert": "^2.0.8" 76 | }, 77 | "scripts": { 78 | "start": "node scripts/start.js", 79 | "build": "node scripts/build.js && npm run gen-service-worker", 80 | "format": "prettier --write 'src/**/*.{js,jsx}' '*.{js,jsx}' 'cosmos/**/*.{js,jsx}' --single-quote", 81 | "test": "node scripts/test.js --env=jsdom", 82 | "gen-service-worker": "sw-precache --config=sw-precache-config.js", 83 | "build-lib": "BABEL_ENV=production babel src/LuckyDraw -d lib --ignore spec.js,test.js", 84 | "build-css": "./node_modules/stylus/bin/stylus -w src/LuckyDraw/Style -o lib/LuckyDraw.css", 85 | "deploy": "gh-pages -d build" 86 | }, 87 | "jest": { 88 | "collectCoverageFrom": [ 89 | "src/**/*.{js,jsx}" 90 | ], 91 | "setupFiles": [ 92 | "/config/polyfills.js" 93 | ], 94 | "testPathIgnorePatterns": [ 95 | "[/\\\\](build|docs|node_modules)[/\\\\]" 96 | ], 97 | "testEnvironment": "node", 98 | "testURL": "http://localhost", 99 | "transform": { 100 | "^.+\\.(js|jsx)$": "/node_modules/babel-jest", 101 | "^.+\\.css$": "/config/jest/cssTransform.js", 102 | "^(?!.*\\.(js|jsx|css|json)$)": "/config/jest/fileTransform.js" 103 | }, 104 | "transformIgnorePatterns": [ 105 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$" 106 | ], 107 | "moduleNameMapper": { 108 | "^react-native$": "react-native-web" 109 | } 110 | }, 111 | "babel": { 112 | "presets": [ 113 | "react-app" 114 | ] 115 | }, 116 | "eslintConfig": { 117 | "extends": "react-app" 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/btn.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * 4 | * bttn.css - https://ganapativs.github.io/bttn.css 5 | * Version - 0.2.4 6 | * Demo: https://bttn.surge.sh 7 | * 8 | * Licensed under the MIT license - http://opensource.org/licenses/MIT 9 | * 10 | * Copyright (c) 2016 Ganapati V S (@ganapativs) 11 | * 12 | */ 13 | /* standalone - .bttn-jelly */ 14 | .bttn-default { 15 | color: #fff; 16 | } 17 | .bttn-primary, 18 | .bttn, 19 | .bttn-lg, 20 | .bttn-md, 21 | .bttn-sm, 22 | .bttn-xs { 23 | color: #1d89ff; 24 | } 25 | .bttn-warning { 26 | color: #feab3a; 27 | } 28 | .bttn-danger { 29 | color: #ff5964; 30 | } 31 | .bttn-success { 32 | color: #28b78d; 33 | } 34 | .bttn-royal { 35 | color: #bd2df5; 36 | } 37 | .bttn, 38 | .bttn-lg, 39 | .bttn-md, 40 | .bttn-sm, 41 | .bttn-xs { 42 | margin: 0; 43 | padding: 0; 44 | border-width: 0; 45 | border-color: transparent; 46 | background: transparent; 47 | font-weight: 400; 48 | cursor: pointer; 49 | position: relative; 50 | } 51 | .bttn-lg { 52 | padding: 8px 15px; 53 | font-size: 24px; 54 | font-family: inherit; 55 | } 56 | .bttn-md { 57 | font-size: 20px; 58 | font-family: inherit; 59 | padding: 5px 12px; 60 | } 61 | .bttn-sm { 62 | padding: 4px 10px; 63 | font-size: 16px; 64 | font-family: inherit; 65 | } 66 | .bttn-xs { 67 | padding: 3px 8px; 68 | font-size: 12px; 69 | font-family: inherit; 70 | } 71 | .bttn-jelly { 72 | margin: 0; 73 | padding: 0; 74 | border-width: 0; 75 | border-color: transparent; 76 | background: transparent; 77 | font-weight: 400; 78 | cursor: pointer; 79 | position: relative; 80 | font-size: 20px; 81 | font-family: inherit; 82 | padding: 5px 12px; 83 | overflow: hidden; 84 | border-radius: 50px; 85 | background: #fff; 86 | color: #1d89ff; 87 | -webkit-transition: all 0.2s cubic-bezier(0.02, 0.01, 0.47, 1); 88 | transition: all 0.2s cubic-bezier(0.02, 0.01, 0.47, 1); 89 | } 90 | .bttn-jelly:before { 91 | position: absolute; 92 | top: 0; 93 | left: 0; 94 | width: 100%; 95 | height: 100%; 96 | border-radius: 50px; 97 | background: currentColor; 98 | content: ''; 99 | z-index: -1; 100 | opacity: 0; 101 | -webkit-transition: all 0.2s cubic-bezier(0.02, 0.01, 0.47, 1); 102 | transition: all 0.2s cubic-bezier(0.02, 0.01, 0.47, 1); 103 | -webkit-transform: scale(0.2); 104 | transform: scale(0.2); 105 | } 106 | .bttn-jelly:hover, 107 | .bttn-jelly:focus { 108 | box-shadow: 0 1px 8px rgba(58,51,53,0.4); 109 | -webkit-transition: all 0.3s cubic-bezier(0.02, 0.01, 0.47, 1); 110 | transition: all 0.3s cubic-bezier(0.02, 0.01, 0.47, 1); 111 | -webkit-transform: scale(1.1); 112 | transform: scale(1.1); 113 | } 114 | .bttn-jelly:hover:before, 115 | .bttn-jelly:focus:before { 116 | opacity: 0.15; 117 | -webkit-transition: all 0.3s cubic-bezier(0.02, 0.01, 0.47, 1); 118 | transition: all 0.3s cubic-bezier(0.02, 0.01, 0.47, 1); 119 | -webkit-transform: scale(1); 120 | transform: scale(1); 121 | } 122 | .bttn-jelly.bttn-xs { 123 | padding: 3px 8px; 124 | font-size: 12px; 125 | font-family: inherit; 126 | } 127 | .bttn-jelly.bttn-xs:hover, 128 | .bttn-jelly.bttn-xs:focus { 129 | box-shadow: 0 1px 4px rgba(58,51,53,0.4); 130 | } 131 | .bttn-jelly.bttn-sm { 132 | padding: 4px 10px; 133 | font-size: 16px; 134 | font-family: inherit; 135 | } 136 | .bttn-jelly.bttn-sm:hover, 137 | .bttn-jelly.bttn-sm:focus { 138 | box-shadow: 0 1px 6px rgba(58,51,53,0.4); 139 | } 140 | .bttn-jelly.bttn-md { 141 | font-size: 20px; 142 | font-family: inherit; 143 | padding: 5px 12px; 144 | } 145 | .bttn-jelly.bttn-md:hover, 146 | .bttn-jelly.bttn-md:focus { 147 | box-shadow: 0 1px 8px rgba(58,51,53,0.4); 148 | } 149 | .bttn-jelly.bttn-lg { 150 | padding: 8px 15px; 151 | font-size: 24px; 152 | font-family: inherit; 153 | } 154 | .bttn-jelly.bttn-lg:hover, 155 | .bttn-jelly.bttn-lg:focus { 156 | box-shadow: 0 1px 10px rgba(58,51,53,0.4); 157 | } 158 | .bttn-jelly.bttn-default { 159 | background: #fff; 160 | color: #1d89ff; 161 | } 162 | .bttn-jelly.bttn-primary { 163 | background: #1d89ff; 164 | color: #fff; 165 | } 166 | .bttn-jelly.bttn-warning { 167 | background: #feab3a; 168 | color: #fff; 169 | } 170 | .bttn-jelly.bttn-danger { 171 | background: #ff5964; 172 | color: #fff; 173 | } 174 | .bttn-jelly.bttn-success { 175 | background: #28b78d; 176 | color: #fff; 177 | } 178 | .bttn-jelly.bttn-royal { 179 | background: #bd2df5; 180 | color: #fff; 181 | } -------------------------------------------------------------------------------- /lib/Arc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require("react"); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _d = require("d3"); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 20 | 21 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 22 | 23 | var Arc = function (_Component) { 24 | _inherits(Arc, _Component); 25 | 26 | function Arc() { 27 | _classCallCheck(this, Arc); 28 | 29 | return _possibleConstructorReturn(this, (Arc.__proto__ || Object.getPrototypeOf(Arc)).apply(this, arguments)); 30 | } 31 | 32 | _createClass(Arc, [{ 33 | key: "renderInnerLabel", 34 | value: function renderInnerLabel(props, Arc) { 35 | var midAngle = props.endAngle < Math.PI ? props.startAngle / 2 + props.endAngle / 2 : props.startAngle / 2 + props.endAngle / 2 + Math.PI; 36 | var textAngle = midAngle * 180 / Math.PI > 180 ? midAngle * 180 / Math.PI - 180 : midAngle * 180 / Math.PI; 37 | return _react2.default.createElement( 38 | "text", 39 | { 40 | className: "rld-value", 41 | transform: "translate(" + Arc.centroid() + ") rotate(" + textAngle + ")", 42 | dy: ".45em", 43 | style: { 44 | shapeRendering: 'crispEdges', 45 | textAnchor: 'middle', 46 | writingMode: props.writingModel, 47 | fontSize: props.fontSize, 48 | fill: props.fontColor 49 | } 50 | }, 51 | props.text 52 | ); 53 | } 54 | }, { 55 | key: "render", 56 | value: function render() { 57 | var props = this.props; 58 | var Arc = (0, _d.arc)().innerRadius(props.innerRadius).outerRadius(props.outerRadius).startAngle(props.startAngle).endAngle(props.endAngle); 59 | 60 | return _react2.default.createElement( 61 | "g", 62 | { className: "rld-compass" }, 63 | _react2.default.createElement("path", { d: Arc(), 64 | fill: props.fill, 65 | stroke: props.stoke 66 | }), 67 | props.showInnerLabels ? this.renderInnerLabel(props, Arc) : null 68 | ); 69 | } 70 | }]); 71 | 72 | return Arc; 73 | }(_react.Component); 74 | 75 | Arc.propTypes = { 76 | text: _react2.default.PropTypes.string, 77 | startAngle: _react2.default.PropTypes.number, 78 | endAngle: _react2.default.PropTypes.number, 79 | innerRadius: _react2.default.PropTypes.number, 80 | outerRadius: _react2.default.PropTypes.number, 81 | labelTextRotate: _react2.default.PropTypes.number, 82 | stoke: _react2.default.PropTypes.number, 83 | showInnerLabels: _react2.default.PropTypes.bool, 84 | fontColor: _react2.default.PropTypes.string, 85 | fontSize: _react2.default.PropTypes.string, 86 | fontFamily: _react2.default.PropTypes.string, 87 | writingModel: _react2.default.PropTypes.string 88 | }; 89 | exports.default = Arc; -------------------------------------------------------------------------------- /lib/Wheel.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require("react"); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _d = require("d3"); 14 | 15 | var _Arc = require("./Arc"); 16 | 17 | var _Arc2 = _interopRequireDefault(_Arc); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 22 | 23 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 24 | 25 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 26 | 27 | var Wheel = function (_Component) { 28 | _inherits(Wheel, _Component); 29 | 30 | function Wheel() { 31 | _classCallCheck(this, Wheel); 32 | 33 | return _possibleConstructorReturn(this, (Wheel.__proto__ || Object.getPrototypeOf(Wheel)).apply(this, arguments)); 34 | } 35 | 36 | _createClass(Wheel, [{ 37 | key: "_processData", 38 | value: function _processData(range) { 39 | var array = []; 40 | for (var i = 0; i < range; i++) { 41 | array.push(100 / range); 42 | } 43 | return array; 44 | } 45 | }, { 46 | key: "render", 47 | value: function render() { 48 | 49 | var props = this.props; 50 | var transform = "translate(" + props.wheelSize / 2 + "," + props.wheelSize / 2 + ") rotate(-" + 180 / props.range + ")"; 51 | var data = this._processData(props.range); 52 | var arcs = (0, _d.pie)()(data).sort(null); 53 | var Pie = arcs.map(function (i, idx) { 54 | var colorIdx = idx > 19 ? idx % 20 : idx; 55 | return _react2.default.createElement(_Arc2.default, { key: idx, 56 | innerRadius: props.innerRadius, 57 | outerRadius: props.outerRadius, 58 | startAngle: i.startAngle, 59 | endAngle: i.endAngle, 60 | showInnerLabels: props.showInnerLabels, 61 | text: props.textArray[idx], 62 | fill: _d.schemeCategory20[colorIdx], 63 | stoke: props.stoke, 64 | fontColor: props.fontColor, 65 | fontSize: props.fontSize, 66 | writingModel: props.writingModel 67 | }); 68 | }); 69 | return _react2.default.createElement( 70 | "svg", 71 | { width: props.wheelSize, height: props.wheelSize }, 72 | _react2.default.createElement( 73 | "g", 74 | { transform: transform }, 75 | Pie 76 | ) 77 | ); 78 | } 79 | }]); 80 | 81 | return Wheel; 82 | }(_react.Component); 83 | 84 | Wheel.propTypes = { 85 | wheelSize: _react2.default.PropTypes.number.isRequired, 86 | range: _react2.default.PropTypes.number.isRequired, 87 | innerRadius: _react2.default.PropTypes.number, 88 | outerRadius: _react2.default.PropTypes.number, 89 | stoke: _react2.default.PropTypes.number, 90 | showInnerLabels: _react2.default.PropTypes.bool, 91 | textArray: _react2.default.PropTypes.array, 92 | fontColor: _react2.default.PropTypes.string, 93 | fontSize: _react2.default.PropTypes.string, 94 | writingModel: _react2.default.PropTypes.string 95 | 96 | }; 97 | Wheel.defaultProps = {}; 98 | exports.default = Wheel; -------------------------------------------------------------------------------- /src/LuckyDraw/LuckyDraw.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Wheel from './Wheel'; 3 | 4 | class LuckyDraw extends Component { 5 | static propTypes = { 6 | width: React.PropTypes.number.isRequired, 7 | height: React.PropTypes.number.isRequired, 8 | range: React.PropTypes.number.isRequired, 9 | wheelSize: React.PropTypes.number, 10 | turns: React.PropTypes.number, 11 | innerRadius: React.PropTypes.number, 12 | outerRadius: React.PropTypes.number, 13 | stoke: React.PropTypes.number, 14 | showInnerLabels: React.PropTypes.bool, 15 | drawLimitSwitch: React.PropTypes.bool, 16 | drawLimit: React.PropTypes.number, 17 | textArray: React.PropTypes.array, 18 | fontColor: React.PropTypes.string, 19 | fontSize: React.PropTypes.string, 20 | writingModel: React.PropTypes.string, 21 | drawButtonLabel: React.PropTypes.string, 22 | ArabicLabel: React.PropTypes.bool, 23 | onSuccessDrawReturn: React.PropTypes.func, 24 | onOutLimitAlert: React.PropTypes.func 25 | }; 26 | static defaultProps = { 27 | width: 500, 28 | height: 350, 29 | stoke: 20, 30 | range: 20, 31 | turns: 3, 32 | rotateSecond: 5, 33 | drawLimit: 3, 34 | drawLimitSwitch: false, 35 | fontColor: '#000', 36 | fontSize: '18px', 37 | writingModel: 'tb', 38 | drawButtonLabel: 'Start', 39 | ArabicLabel: false, 40 | textArray: [] 41 | }; 42 | 43 | constructor(props) { 44 | super(props); 45 | if (!('wheelSize' in props)) { 46 | this.props.wheelSize = this.defaultProps.width * 2; 47 | } 48 | this.state = { 49 | startDraw: false, 50 | drawTimes: 1, 51 | randomNumber: null, 52 | rolling: false 53 | }; 54 | } 55 | 56 | // shouldComponentUpdate(nextProps, nextState) { 57 | // return true 58 | // } 59 | 60 | _processRandomNumber(min, max) { 61 | return Math.floor(Math.random() * (max - min + 1)) + min; 62 | } 63 | 64 | _processDrawAngle(range, turns, drawTimes, drawNumber) { 65 | const peer = 360 / range; 66 | const totalAngle = 360 * turns * drawTimes + drawNumber * peer; 67 | return totalAngle; 68 | } 69 | 70 | _processDrawing(e) { 71 | e.preventDefault(); 72 | if (!this.state.rolling) { 73 | let drawTime = this.state.drawTimes; 74 | if (this.props.drawLimitSwitch && drawTime - 1 < this.props.drawLimit) { 75 | this.setState({ 76 | startDraw: true, 77 | rolling: true, 78 | randomNumber: this._processRandomNumber(0, this.props.range - 1), 79 | drawTimes: this.state.drawTimes + 1 80 | }); 81 | setTimeout( 82 | () => { 83 | this.setState({ 84 | rolling: false 85 | }); 86 | this.props.onSuccessDrawReturn(this.state.randomNumber); 87 | }, 88 | this.props.rotateSecond * 1000 89 | ); 90 | } else { 91 | this.props.onOutLimitAlert(true); 92 | } 93 | } 94 | } 95 | 96 | render() { 97 | const state = this.state; 98 | const props = this.props; 99 | let transformRotate = state.startDraw 100 | ? this._processDrawAngle( 101 | props.range, 102 | props.turns, 103 | state.drawTimes, 104 | state.randomNumber 105 | ) 106 | : 0; 107 | return ( 108 |
115 |
120 |
121 |
122 |
131 | 132 |
133 |
134 |
135 | 143 |
144 |
145 |
146 | ); 147 | } 148 | } 149 | 150 | export default LuckyDraw; 151 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; 3 | import sweetAlert from 'sweetalert'; 4 | import LuckyDraw from './LuckyDraw'; 5 | import 'sweetalert/dist/sweetalert.css'; 6 | import 'highlight.js/styles/googlecode.css'; 7 | import './App.styl'; 8 | import '../lib/LuckyDraw.css'; 9 | import './btn.css'; 10 | // import Highlight from 'react-highlight'; 11 | 12 | 13 | class App extends Component { 14 | constructor(porps) { 15 | super(porps); 16 | this.state = { 17 | currentTab: 0 18 | }; 19 | } 20 | 21 | changeTab = function (key) { 22 | console.log(key); 23 | // this.setState({currentTab: key}) 24 | }; 25 | 26 | render() { 27 | const example2 = [ 28 | 'Hello', 29 | 'world', 30 | 'php', 31 | 'is', 32 | 'best', 33 | 'language', 34 | 'in', 35 | 'the', 36 | ]; 37 | const example3 = [ 38 | 'javascript is best language', 39 | 'php is best language', 40 | 'python is best language', 41 | 'ruby is best language', 42 | 'go is best language', 43 | 'swift is best language', 44 | 'dart is best language', 45 | 'rust is best language', 46 | 'C is best language', 47 | 'C++ is best language', 48 | 'lua is best language', 49 | 'xml is best language', 50 | 'html is best language', 51 | 'Visual Basic .NET is best language', 52 | 'Delphi/Object Pascal is best language', 53 | 'C# is best language', 54 | 'Objective-C is best language', 55 | 'Assembly language is best language', 56 | 'MATLAB is best language', 57 | 'PL/SQL is best language', 58 | 'Scratch is best language', 59 | 'R is best language', 60 | 'Visual Basic is best language' 61 | ]; 62 | const example4 = [ 63 | 'Big Prize 1', 64 | 'Big Prize 2', 65 | 'Big Prize 3', 66 | 'Big Prize 4', 67 | 'Big Prize 5', 68 | 'Big Prize 6', 69 | 'Big Prize 7', 70 | 'Big Prize 8', 71 | 'Big Prize 9', 72 | 'Big Prize 10' 73 | ]; 74 | return ( 75 |
76 |

React-luckydraw

77 | 78 | 79 | 80 | based 81 | custom with charters 82 | vertical label 83 | effect with confetti 84 | 85 | 86 | 87 |

based

88 | { 104 | sweetAlert('Got ' + (drawNumber + 1).toString(), 'Congratulations !', "success"); 105 | }} 106 | onOutLimitAlert={limit => { 107 | if (limit) { 108 | sweetAlert("Oops...", "out of limits!!", "error"); 109 | } 110 | }} 111 | /> 112 | 113 |
114 | 115 |

custom with charters

116 | { 132 | sweetAlert(example2[drawNumber], 'Congratulations !', "success"); 133 | 134 | }} 135 | onOutLimitAlert={limit => { 136 | if (limit) { 137 | sweetAlert("Oops...", "out of limits!!", "error"); 138 | } 139 | }} 140 | /> 141 |
142 | 143 | 144 |

vertical label

145 | { 161 | sweetAlert(example3[drawNumber], 'Congratulations !', "success"); 162 | 163 | }} 164 | onOutLimitAlert={limit => { 165 | if (limit) { 166 | sweetAlert("Oops...", "out of limits!!", "error"); 167 | } 168 | }} 169 | /> 170 |
171 | 172 | 173 |

Effect with confetti

174 | 175 | { 191 | 192 | sweetAlert(example4[drawNumber], 'Congratulations !', "success"); 193 | 194 | }} 195 | onOutLimitAlert={limit => { 196 | if (limit) { 197 | sweetAlert("Oops...", "out of limits!!", "error"); 198 | } 199 | }} 200 | /> 201 |
202 |
203 | 204 |
205 | ); 206 | } 207 | } 208 | 209 | export default App; 210 | -------------------------------------------------------------------------------- /lib/LuckyDraw.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require("react"); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _Wheel = require("./Wheel"); 14 | 15 | var _Wheel2 = _interopRequireDefault(_Wheel); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 22 | 23 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 24 | 25 | var LuckyDraw = function (_Component) { 26 | _inherits(LuckyDraw, _Component); 27 | 28 | function LuckyDraw(props) { 29 | _classCallCheck(this, LuckyDraw); 30 | 31 | var _this = _possibleConstructorReturn(this, (LuckyDraw.__proto__ || Object.getPrototypeOf(LuckyDraw)).call(this, props)); 32 | 33 | if (!('wheelSize' in props)) { 34 | _this.props.wheelSize = _this.defaultProps.width * 2; 35 | } 36 | _this.state = { 37 | startDraw: false, 38 | drawTimes: 1, 39 | randomNumber: null, 40 | rolling: false 41 | }; 42 | return _this; 43 | } 44 | 45 | _createClass(LuckyDraw, [{ 46 | key: "shouldComponentUpdate", 47 | value: function shouldComponentUpdate(nextProps, nextState) { 48 | return true; 49 | } 50 | }, { 51 | key: "_processRandomNumber", 52 | value: function _processRandomNumber(min, max) { 53 | return Math.floor(Math.random() * (max - min + 1)) + min; 54 | } 55 | }, { 56 | key: "_processDrawAngle", 57 | value: function _processDrawAngle(range, turns, drawTimes, drawNumber) { 58 | var peer = 360 / range; 59 | var totalAngle = 360 * turns * drawTimes + drawNumber * peer; 60 | return totalAngle; 61 | } 62 | }, { 63 | key: "_processDrawing", 64 | value: function _processDrawing(e) { 65 | var _this2 = this; 66 | 67 | e.preventDefault(); 68 | if (!this.state.rolling) { 69 | var drawTime = this.state.drawTimes; 70 | if (this.props.drawLimitSwitch && drawTime - 1 < this.props.drawLimit) { 71 | this.setState({ 72 | startDraw: true, 73 | rolling: true, 74 | randomNumber: this._processRandomNumber(0, this.props.range - 1), 75 | drawTimes: this.state.drawTimes + 1 76 | }); 77 | setTimeout(function () { 78 | _this2.setState({ 79 | rolling: false 80 | }); 81 | _this2.props.onSuccessDrawReturn(_this2.state.randomNumber); 82 | }, this.props.rotateSecond * 1000); 83 | } else { 84 | this.props.onOutLimitAlert(true); 85 | } 86 | } 87 | } 88 | }, { 89 | key: "render", 90 | value: function render() { 91 | var _this3 = this; 92 | 93 | var state = this.state; 94 | var props = this.props; 95 | var transformRotate = state.startDraw ? this._processDrawAngle(props.range, props.turns, state.drawTimes, state.randomNumber) : 0; 96 | return _react2.default.createElement( 97 | "div", 98 | { className: "react_luckyDraw", style: { 99 | width: props.width, 100 | height: props.height 101 | } }, 102 | _react2.default.createElement( 103 | "div", 104 | { className: state.rolling ? 'compass__container rolling' : 'compass__container' }, 105 | _react2.default.createElement( 106 | "div", 107 | { className: "control__panel" }, 108 | _react2.default.createElement("div", { className: "compass__arrow" }), 109 | _react2.default.createElement( 110 | "div", 111 | { className: "compass__spin", 112 | style: { 113 | width: props.wheelSize + "px", 114 | height: props.wheelSize + "px", 115 | transform: "rotate(" + -transformRotate + "deg) translate3d(0,0,-1px)", 116 | transitionDuration: props.rotateSecond + "s" 117 | } }, 118 | _react2.default.createElement(_Wheel2.default, props) 119 | ) 120 | ), 121 | _react2.default.createElement( 122 | "div", 123 | { className: "compass__btn" }, 124 | _react2.default.createElement( 125 | "button", 126 | { 127 | className: "bttn-jelly bttn-md bttn-danger", 128 | onClick: function onClick(e) { 129 | _this3._processDrawing(e); 130 | } }, 131 | props.drawButtonLabel 132 | ) 133 | ) 134 | ) 135 | ); 136 | } 137 | }]); 138 | 139 | return LuckyDraw; 140 | }(_react.Component); 141 | 142 | LuckyDraw.propTypes = { 143 | width: _react2.default.PropTypes.number.isRequired, 144 | height: _react2.default.PropTypes.number.isRequired, 145 | range: _react2.default.PropTypes.number.isRequired, 146 | wheelSize: _react2.default.PropTypes.number, 147 | turns: _react2.default.PropTypes.number, 148 | innerRadius: _react2.default.PropTypes.number, 149 | outerRadius: _react2.default.PropTypes.number, 150 | stoke: _react2.default.PropTypes.number, 151 | showInnerLabels: _react2.default.PropTypes.bool, 152 | drawLimitSwitch: _react2.default.PropTypes.bool, 153 | drawLimit: _react2.default.PropTypes.number, 154 | textArray: _react2.default.PropTypes.array, 155 | fontColor: _react2.default.PropTypes.string, 156 | fontSize: _react2.default.PropTypes.string, 157 | writingModel: _react2.default.PropTypes.string, 158 | drawButtonLabel: _react2.default.PropTypes.string, 159 | onSuccessDrawReturn: _react2.default.PropTypes.func, 160 | onOutLimitAlert: _react2.default.PropTypes.func 161 | }; 162 | LuckyDraw.defaultProps = { 163 | width: 500, 164 | height: 350, 165 | stoke: 20, 166 | range: 20, 167 | turns: 3, 168 | rotateSecond: 5, 169 | drawLimit: 3, 170 | drawLimitSwitch: false, 171 | fontColor: '#000', 172 | fontSize: '18px', 173 | writingModel: 'tb', 174 | drawButtonLabel: 'Start' 175 | }; 176 | exports.default = LuckyDraw; -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | // Do this as the first thing so that any code reading it knows the right env. 2 | process.env.NODE_ENV = 'production'; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | var chalk = require('chalk'); 11 | var fs = require('fs-extra'); 12 | var path = require('path'); 13 | var pathExists = require('path-exists'); 14 | var filesize = require('filesize'); 15 | var gzipSize = require('gzip-size').sync; 16 | var webpack = require('webpack'); 17 | var config = require('../config/webpack.config.prod'); 18 | var paths = require('../config/paths'); 19 | var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 20 | var recursive = require('recursive-readdir'); 21 | var stripAnsi = require('strip-ansi'); 22 | 23 | var useYarn = pathExists.sync(paths.yarnLockFile); 24 | 25 | // Warn and crash if required files are missing 26 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 27 | process.exit(1); 28 | } 29 | 30 | // Input: /User/dan/app/build/static/js/main.82be8.js 31 | // Output: /static/js/main.js 32 | function removeFileNameHash(fileName) { 33 | return fileName 34 | .replace(paths.appBuild, '') 35 | .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3); 36 | } 37 | 38 | // Input: 1024, 2048 39 | // Output: "(+1 KB)" 40 | function getDifferenceLabel(currentSize, previousSize) { 41 | var FIFTY_KILOBYTES = 1024 * 50; 42 | var difference = currentSize - previousSize; 43 | var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; 44 | if (difference >= FIFTY_KILOBYTES) { 45 | return chalk.red('+' + fileSize); 46 | } else if (difference < FIFTY_KILOBYTES && difference > 0) { 47 | return chalk.yellow('+' + fileSize); 48 | } else if (difference < 0) { 49 | return chalk.green(fileSize); 50 | } else { 51 | return ''; 52 | } 53 | } 54 | 55 | // First, read the current file sizes in build directory. 56 | // This lets us display how much they changed later. 57 | recursive(paths.appBuild, (err, fileNames) => { 58 | var previousSizeMap = (fileNames || []) 59 | .filter(fileName => /\.(js|css)$/.test(fileName)) 60 | .reduce((memo, fileName) => { 61 | var contents = fs.readFileSync(fileName); 62 | var key = removeFileNameHash(fileName); 63 | memo[key] = gzipSize(contents); 64 | return memo; 65 | }, {}); 66 | 67 | // Remove all content but keep the directory so that 68 | // if you're in it, you don't end up in Trash 69 | fs.emptyDirSync(paths.appBuild); 70 | 71 | // Start the webpack build 72 | build(previousSizeMap); 73 | 74 | // Merge with the public folder 75 | copyPublicFolder(); 76 | }); 77 | 78 | // Print a detailed summary of build files. 79 | function printFileSizes(stats, previousSizeMap) { 80 | var assets = stats.toJson().assets 81 | .filter(asset => /\.(js|css)$/.test(asset.name)) 82 | .map(asset => { 83 | var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name); 84 | var size = gzipSize(fileContents); 85 | var previousSize = previousSizeMap[removeFileNameHash(asset.name)]; 86 | var difference = getDifferenceLabel(size, previousSize); 87 | return { 88 | folder: path.join('build', path.dirname(asset.name)), 89 | name: path.basename(asset.name), 90 | size: size, 91 | sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '') 92 | }; 93 | }); 94 | assets.sort((a, b) => b.size - a.size); 95 | var longestSizeLabelLength = Math.max.apply(null, 96 | assets.map(a => stripAnsi(a.sizeLabel).length) 97 | ); 98 | assets.forEach(asset => { 99 | var sizeLabel = asset.sizeLabel; 100 | var sizeLength = stripAnsi(sizeLabel).length; 101 | if (sizeLength < longestSizeLabelLength) { 102 | var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength); 103 | sizeLabel += rightPadding; 104 | } 105 | console.log( 106 | ' ' + sizeLabel + 107 | ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name) 108 | ); 109 | }); 110 | } 111 | 112 | // Print out errors 113 | function printErrors(summary, errors) { 114 | console.log(chalk.red(summary)); 115 | console.log(); 116 | errors.forEach(err => { 117 | console.log(err.message || err); 118 | console.log(); 119 | }); 120 | } 121 | 122 | // Create the production build and print the deployment instructions. 123 | function build(previousSizeMap) { 124 | console.log('Creating an optimized production build...'); 125 | webpack(config).run((err, stats) => { 126 | if (err) { 127 | printErrors('Failed to compile.', [err]); 128 | process.exit(1); 129 | } 130 | 131 | if (stats.compilation.errors.length) { 132 | printErrors('Failed to compile.', stats.compilation.errors); 133 | process.exit(1); 134 | } 135 | 136 | if (process.env.CI && stats.compilation.warnings.length) { 137 | printErrors('Failed to compile.', stats.compilation.warnings); 138 | process.exit(1); 139 | } 140 | 141 | console.log(chalk.green('Compiled successfully.')); 142 | console.log(); 143 | 144 | console.log('File sizes after gzip:'); 145 | console.log(); 146 | printFileSizes(stats, previousSizeMap); 147 | console.log(); 148 | 149 | var openCommand = process.platform === 'win32' ? 'start' : 'open'; 150 | var appPackage = require(paths.appPackageJson); 151 | var homepagePath = appPackage.homepage; 152 | var publicPath = config.output.publicPath; 153 | if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) { 154 | // "homepage": "http://user.github.io/project" 155 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.'); 156 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 157 | console.log(); 158 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.'); 159 | console.log('To publish it at ' + chalk.green(homepagePath) + ', run:'); 160 | // If script deploy has been added to package.json, skip the instructions 161 | if (typeof appPackage.scripts.deploy === 'undefined') { 162 | console.log(); 163 | if (useYarn) { 164 | console.log(' ' + chalk.cyan('yarn') + ' add --dev gh-pages'); 165 | } else { 166 | console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages'); 167 | } 168 | console.log(); 169 | console.log('Add the following script in your ' + chalk.cyan('package.json') + '.'); 170 | console.log(); 171 | console.log(' ' + chalk.dim('// ...')); 172 | console.log(' ' + chalk.yellow('"scripts"') + ': {'); 173 | console.log(' ' + chalk.dim('// ...')); 174 | console.log(' ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"npm run build&&gh-pages -d build"')); 175 | console.log(' }'); 176 | console.log(); 177 | console.log('Then run:'); 178 | } 179 | console.log(); 180 | console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy'); 181 | console.log(); 182 | } else if (publicPath !== '/') { 183 | // "homepage": "http://mywebsite.com/project" 184 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.'); 185 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 186 | console.log(); 187 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.'); 188 | console.log(); 189 | } else { 190 | // no homepage or "homepage": "http://mywebsite.com" 191 | console.log('The project was built assuming it is hosted at the server root.'); 192 | if (homepagePath) { 193 | // "homepage": "http://mywebsite.com" 194 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 195 | console.log(); 196 | } else { 197 | // no homepage 198 | console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.'); 199 | console.log('For example, add this to build it for GitHub Pages:') 200 | console.log(); 201 | console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(',')); 202 | console.log(); 203 | } 204 | console.log('The ' + chalk.cyan('build') + ' folder is ready to be deployed.'); 205 | console.log('You may also serve it locally with a static server:') 206 | console.log(); 207 | if (useYarn) { 208 | console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server'); 209 | } else { 210 | console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server'); 211 | } 212 | console.log(' ' + chalk.cyan('pushstate-server') + ' build'); 213 | console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000'); 214 | console.log(); 215 | } 216 | }); 217 | } 218 | 219 | function copyPublicFolder() { 220 | fs.copySync(paths.appPublic, paths.appBuild, { 221 | dereference: true, 222 | filter: file => file !== paths.appHtml 223 | }); 224 | } 225 | -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var autoprefixer = require('autoprefixer'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 5 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 6 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); 7 | var getClientEnvironment = require('./env'); 8 | var paths = require('./paths'); 9 | 10 | 11 | // Webpack uses `publicPath` to determine where the app is being served from. 12 | // In development, we always serve from the root. This makes config easier. 13 | var publicPath = '/'; 14 | // `publicUrl` is just like `publicPath`, but we will provide it to our app 15 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. 16 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. 17 | var publicUrl = ''; 18 | // Get environment variables to inject into our app. 19 | var env = getClientEnvironment(publicUrl); 20 | 21 | // This is the development configuration. 22 | // It is focused on developer experience and fast rebuilds. 23 | // The production configuration is different and lives in a separate file. 24 | module.exports = { 25 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools. 26 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. 27 | devtool: 'cheap-module-source-map', 28 | // These are the "entry points" to our application. 29 | // This means they will be the "root" imports that are included in JS bundle. 30 | // The first two entry points enable "hot" CSS and auto-refreshes for JS. 31 | entry: [ 32 | // Include an alternative client for WebpackDevServer. A client's job is to 33 | // connect to WebpackDevServer by a socket and get notified about changes. 34 | // When you save a file, the client will either apply hot updates (in case 35 | // of CSS changes), or refresh the page (in case of JS changes). When you 36 | // make a syntax error, this client will display a syntax error overlay. 37 | // Note: instead of the default WebpackDevServer client, we use a custom one 38 | // to bring better experience for Create React App users. You can replace 39 | // the line below with these two lines if you prefer the stock client: 40 | // require.resolve('webpack-dev-server/client') + '?/', 41 | // require.resolve('webpack/hot/dev-server'), 42 | require.resolve('react-dev-utils/webpackHotDevClient'), 43 | // We ship a few polyfills by default: 44 | require.resolve('./polyfills'), 45 | // Finally, this is your app's code: 46 | paths.appIndexJs 47 | // We include the app code last so that if there is a runtime error during 48 | // initialization, it doesn't blow up the WebpackDevServer client, and 49 | // changing JS code would still trigger a refresh. 50 | ], 51 | output: { 52 | // Next line is not used in dev but WebpackDevServer crashes without it: 53 | path: paths.appBuild, 54 | // Add /* filename */ comments to generated require()s in the output. 55 | pathinfo: true, 56 | // This does not produce a real file. It's just the virtual path that is 57 | // served by WebpackDevServer in development. This is the JS bundle 58 | // containing code from all our entry points, and the Webpack runtime. 59 | filename: 'static/js/bundle.js', 60 | // This is the URL that app is served from. We use "/" in development. 61 | publicPath: publicPath 62 | }, 63 | resolve: { 64 | // This allows you to set a fallback for where Webpack should look for modules. 65 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here. 66 | // We use `fallback` instead of `root` because we want `node_modules` to "win" 67 | // if there any conflicts. This matches Node resolution mechanism. 68 | // https://github.com/facebookincubator/create-react-app/issues/253 69 | fallback: paths.nodePaths, 70 | // These are the reasonable defaults supported by the Node ecosystem. 71 | // We also include JSX as a common component filename extension to support 72 | // some tools, although we do not recommend using it, see: 73 | // https://github.com/facebookincubator/create-react-app/issues/290 74 | extensions: ['.js', '.json', '.jsx', '.styl',''], 75 | alias: { 76 | // Support React Native Web 77 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 78 | 'react-native': 'react-native-web' 79 | } 80 | }, 81 | 82 | module: { 83 | // First, run the linter. 84 | // It's important to do this before Babel processes the JS. 85 | preLoaders: [ 86 | { 87 | test: /\.(js|jsx)$/, 88 | loader: 'eslint', 89 | include: paths.appSrc, 90 | } 91 | ], 92 | loaders: [ 93 | // Default loader: load all assets that are not handled 94 | // by other loaders with the url loader. 95 | // Note: This list needs to be updated with every change of extensions 96 | // the other loaders match. 97 | // E.g., when adding a loader for a new supported file extension, 98 | // we need to add the supported extension to this loader too. 99 | // Add one new line in `exclude` for each loader. 100 | // 101 | // "file" loader makes sure those assets get served by WebpackDevServer. 102 | // When you `import` an asset, you get its (virtual) filename. 103 | // In production, they would get copied to the `build` folder. 104 | // "url" loader works like "file" loader except that it embeds assets 105 | // smaller than specified limit in bytes as data URLs to avoid requests. 106 | // A missing `test` is equivalent to a match. 107 | { 108 | exclude: [ 109 | /\.html$/, 110 | /\.(js|jsx)$/, 111 | /\.css$/, 112 | /\.styl$/, 113 | /\.json$/, 114 | /\.svg$/ 115 | ], 116 | loader: 'url', 117 | query: { 118 | limit: 10000, 119 | name: 'static/media/[name].[hash:8].[ext]' 120 | } 121 | }, 122 | // Process JS with Babel. 123 | { 124 | test: /\.(js|jsx)$/, 125 | include: paths.appSrc, 126 | loader: 'babel', 127 | query: { 128 | 129 | // This is a feature of `babel-loader` for webpack (not Babel itself). 130 | // It enables caching results in ./node_modules/.cache/babel-loader/ 131 | // directory for faster rebuilds. 132 | cacheDirectory: true 133 | } 134 | }, 135 | // "postcss" loader applies autoprefixer to our CSS. 136 | // "css" loader resolves paths in CSS and adds assets as dependencies. 137 | // "style" loader turns CSS into JS modules that inject