Merge pull request #77 from kevinsqi/typescript

Convert project to Typescript and improve demo setup
This commit is contained in:
Kevin Qi 2019-04-21 18:41:18 -07:00 committed by GitHub
commit 23923a888c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 15089 additions and 27005 deletions

View File

@ -1,6 +0,0 @@
{
"presets": [
"react",
"env"
]
}

View File

@ -1,19 +0,0 @@
{
"extends": "airbnb",
"rules": {
"arrow-parens": ["error", "always"],
"comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline"
}
],
"max-len": 0,
"react/jsx-filename-extension": 0
},
"env": {
"browser": true,
"mocha": true
}
}

17
.gitignore vendored
View File

@ -1,6 +1,15 @@
node_modules
npm-debug.log
# dependencies
/node_modules
# distribution folder
/dist
# coverage
/coverage
.DS_Store
.cache
*.iml
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea

View File

@ -1,8 +1,4 @@
language: node_js
node_js:
- "node"
- "7"
- "8"
script:
- npm run lint
- npm test
- node
- lts/*

View File

@ -1,10 +1,10 @@
# Contributing
## Contributing
To run demo locally on localhost:8080:
Thanks for your interest in helping out! If you'd like to make a change to react-circular-progressbar master, it's preferable to first validate your change by writing what you plan to do in a new [Github issue](https://github.com/kevinsqi/react-circular-progressbar/issues).
```bash
yarn install
yarn start
```
To make your own changes:
Keep tests passing by running `yarn test` and `yarn run lint`.
1. Fork the repo.
2. Go to `/demo` and view the README to see how to run the demo.
3. Make changes to `/src` and they'll be applied immediately to the running demo. You can use this to prototype, but you probably don't need to commit the changes to `/demo` when creating a PR.
4. Add tests for your new changes and make sure they're passing with `yarn test`.

View File

@ -6,7 +6,7 @@
A circular progressbar component, built with SVG and extensively customizable. [**Try it out on CodeSandbox**](https://codesandbox.io/s/vymm4oln6y).
<a href="https://codesandbox.io/s/vymm4oln6y"><img height="120" src="/docs/animated-progressbar.gif?raw=true" alt="animated progressbar" /></a> <a href="https://codesandbox.io/s/vymm4oln6y"><img height="120" src="/docs/circular-progressbar-examples.png?raw=true" alt="progressbar examples" /></a>
<a href="https://codesandbox.io/s/vymm4oln6y"><img height="120" src="/demo/public/images/animated-progressbar.gif?raw=true" alt="animated progressbar" /></a> <a href="https://codesandbox.io/s/vymm4oln6y"><img height="120" src="/demo/public/images/circular-progressbar-examples.png?raw=true" alt="progressbar examples" /></a>
## Installation
@ -139,7 +139,7 @@ If you want to add images or multiple lines of text within the progressbar, the
[**Here's a Codesandbox demo**](https://codesandbox.io/s/qlr7w0rm29)
<a href="https://codesandbox.io/s/qlr7w0rm29"><img src="/docs/custom-content-progressbar.png?raw=true" alt="custom content progressbar" /></a>
<a href="https://codesandbox.io/s/qlr7w0rm29"><img src="/demo/public/images/custom-content-progressbar.png?raw=true" alt="custom content progressbar" /></a>
## Customizing animation and animating text

5
demo/.env Normal file
View File

@ -0,0 +1,5 @@
# This is used to skip the error where
# e.g. babel-jest is a sub-dependency in the parent dir
# but is also used by react-scripts:
# "There might be a problem with the project dependency tree"
SKIP_PREFLIGHT_CHECK=true

23
demo/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

27
demo/README.md Normal file
View File

@ -0,0 +1,27 @@
# react-circular-progressbar demo site
## Developing
In the parent react-circular-progressbar directory, run:
```
yarn link
yarn start
```
In this repo, run:
```
yarn link react-circular-progressbar
yarn start
```
The demo site will be running at [localhost:3000](http://localhost:3000). Now, any changes that are made to react-circular-progressbar source will be live-reloaded on the demo site.
## Deploying
This site is hosted on github pages at https://www.kevinqi.com/react-circular-progressbar. Deploy new updates by running in this directory:
```
yarn run deploy
```

35
demo/package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "demo",
"homepage": "https://www.kevinqi.com/react-circular-progressbar",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "24.0.11",
"@types/node": "11.13.6",
"@types/react": "16.8.14",
"@types/react-dom": "16.8.4",
"gh-pages": "^2.0.1",
"react": "^16.8.6",
"react-circular-progressbar": "file:./../",
"react-dom": "^16.8.6",
"react-scripts": "2.1.8",
"typescript": "3.4.4"
},
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

BIN
demo/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

43
demo/public/index.html Normal file
View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>react-circular-progressbar: a circular progress indicator component</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
demo/public/manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

25
demo/src/App.css Normal file
View File

@ -0,0 +1,25 @@
.bg-yellow {
background-color: #f8e8d5;
}
/* bootstrap overrides */
a {
color: #3e98c7;
}
a:hover {
text-decoration: none;
}
/* demo style overrides */
.CircularProgressbar.incomplete .CircularProgressbar-path {
stroke: #f66;
}
.CircularProgressbar.incomplete .CircularProgressbar-text {
fill: #f66;
}
.CircularProgressbar.complete .CircularProgressbar-path {
stroke: #99f;
}
.CircularProgressbar.complete .CircularProgressbar-text {
fill: #99f;
}

9
demo/src/App.test.tsx Normal file
View File

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

19
demo/src/App.tsx Normal file
View File

@ -0,0 +1,19 @@
import React, { Component } from 'react';
import CircularProgressbar from 'react-circular-progressbar';
import Demo from './Demo';
// Stylesheets
import 'react-circular-progressbar/dist/styles.css';
import './App.css';
class App extends Component {
render() {
return (
<div>
<Demo />
</div>
);
}
}
export default App;

View File

@ -0,0 +1,71 @@
import React from 'react';
import CircularProgressbar from 'react-circular-progressbar';
type CircularProgressbarProps = {
counterClockwise?: boolean;
strokeWidth?: number;
};
type Props = CircularProgressbarProps &
typeof ChangingProgressbar.defaultProps & {
percentages: number[];
classForPercentage?: (percentage: number) => string;
stylesForPercentage?: (percentage: number) => {};
textForPercentage?: (percentage: number) => string;
};
type State = {
currentPercentageIndex: number;
};
class ChangingProgressbar extends React.Component<Props, State> {
static defaultProps = {
interval: 1000,
classForPercentage: (percentage: number) => '',
stylesForPercentage: (percentage: number) => ({}),
textForPercentage: (percentage: number) => `${percentage}%`,
};
state = {
currentPercentageIndex: 0,
};
componentDidMount() {
setInterval(() => {
this.setState({
currentPercentageIndex:
(this.state.currentPercentageIndex + 1) % this.props.percentages.length,
});
}, this.props.interval);
}
getCurrentPercentage() {
return this.props.percentages[this.state.currentPercentageIndex];
}
getClassName() {
return this.props.classForPercentage(this.getCurrentPercentage());
}
getStyles() {
return this.props.stylesForPercentage(this.getCurrentPercentage());
}
getText() {
return this.props.textForPercentage(this.getCurrentPercentage());
}
render() {
return (
<CircularProgressbar
{...this.props}
className={this.getClassName()}
percentage={this.getCurrentPercentage()}
text={this.getText()}
styles={this.getStyles()}
/>
);
}
}
export default ChangingProgressbar;

123
demo/src/Demo.tsx Normal file
View File

@ -0,0 +1,123 @@
import React from 'react';
import CircularProgressbar from 'react-circular-progressbar';
import ChangingProgressbar from './ChangingProgressbar';
const githubURL = 'https://github.com/kevinsqi/react-circular-progressbar';
const Example: React.FunctionComponent<{ description: string }> = ({ description, children }) => (
<div className="col-12 col-sm-6 col-md-3">
<div className="row mb-1">
<div className="col-6 offset-3">{children}</div>
</div>
<p className="text-center">{description}</p>
</div>
);
function Demo() {
return (
<div className="container">
<div className="row mt-5">
<div className="col-12">
<div className="text-center">
<h1 className="mb-3">react-circular-progressbar</h1>
<p>A circular progress indicator component</p>
</div>
</div>
</div>
<div className="row mt-5 mb-5">
<div className="col-6 offset-3 col-md-2 offset-md-5">
<ChangingProgressbar
percentages={[0, 20, 40, 60, 80, 100]}
stylesForPercentage={(percentage: number) => {
const alpha = (100 + percentage) / 200;
return {
path: {
stroke: `rgba(62, 152, 199, ${alpha})`,
},
};
}}
/>
</div>
</div>
<hr />
<div className="row mt-5">
<div className="col-12 mb-5">
<h2 className="text-center">Built with SVG and styled with plain CSS.</h2>
</div>
<Example description="Customize text and styling dynamically based on percentage.">
<ChangingProgressbar
percentages={[75, 100]}
classForPercentage={(percentage: number) => {
return percentage === 100 ? 'complete' : 'incomplete';
}}
textForPercentage={(percentage: number) => {
return percentage === 100 ? `${percentage}!!` : `${percentage}`;
}}
/>
</Example>
<Example description="Customize stroke width and make it go counterclockwise.">
<ChangingProgressbar percentages={[0, 20, 80]} strokeWidth={5} counterClockwise />
</Example>
<Example description="Add a background color for that inverted look.">
<CircularProgressbar
className="CircularProgressbar-inverted"
background
backgroundPadding={5}
strokeWidth={6}
percentage={66}
text={`${66}%`}
classes={{
root: 'CircularProgressbar',
trail: 'CircularProgressbar-trail',
path: 'CircularProgressbar-path',
text: 'CircularProgressbar-text some-additional-test-class',
background: 'CircularProgressbar-background',
}}
styles={{
background: {
fill: '#3e98c7',
},
}}
/>
</Example>
<Example description="With SVG and CSS you can do whatever you want.">
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', width: '100%' }}>
<CircularProgressbar percentage={50} />
</div>
<div style={{ width: '100%', padding: '10%' }}>
<img style={{ width: '100%' }} src="https://i.imgur.com/b9NyUGm.png" alt="doge" />
</div>
</div>
</Example>
</div>
<hr />
<div className="mt-5 mb-5">
<h2 className="text-center">Installation</h2>
<div className="text-center mt-5">
<p>Install with yarn or npm:</p>
<p className="mb-5">
<code className="p-2 text-dark bg-yellow">yarn add react-circular-progressbar</code>
</p>
<a className="btn btn-info btn-lg" href={githubURL}>
View docs on Github
</a>
</div>
<div className="text-center">
<div className="mt-5">
Built by <a href="https://www.kevinqi.com/">@kevinsqi</a>
</div>
</div>
</div>
</div>
);
}
export default Demo;

14
demo/src/index.css Normal file
View File

@ -0,0 +1,14 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

12
demo/src/index.tsx Normal file
View File

@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

1
demo/src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

143
demo/src/serviceWorker.ts Normal file
View File

@ -0,0 +1,143 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

25
demo/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}

10720
demo/yarn.lock Executable file

File diff suppressed because it is too large Load Diff

1
dist/index.js vendored

File diff suppressed because one or more lines are too long

61
dist/styles.css vendored
View File

@ -1,61 +0,0 @@
/*
* react-circular-progressbar styles
*
* All of the styles in this file are optional and configurable!
*/
.CircularProgressbar {
/*
* This fixes an issue where the CircularProgressbar svg has
* 0 width inside a "display: flex" container, and thus not visible.
*
* If you're not using "display: flex", you can remove this style.
*/
width: 100%;
}
.CircularProgressbar .CircularProgressbar-path {
stroke: #3e98c7;
stroke-linecap: round;
transition: stroke-dashoffset 0.5s ease 0s;
}
.CircularProgressbar .CircularProgressbar-trail {
stroke: #d6d6d6;
}
.CircularProgressbar .CircularProgressbar-text {
fill: #3e98c7;
font-size: 20px;
dominant-baseline: middle;
text-anchor: middle;
}
.CircularProgressbar .CircularProgressbar-background {
fill: #d6d6d6;
}
/*
* Sample background styles. Use these with e.g.:
*
* <CircularProgressbar
* className="CircularProgressbar-inverted"
* background
* percentage={50}
* />
*/
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-background {
fill: #3e98c7;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-text {
fill: #fff;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-path {
stroke: #fff;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-trail {
stroke: transparent;
}

View File

@ -1,53 +0,0 @@
import React from 'react';
import CircularProgressbar from '../src';
class ChangingProgressbar extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPercentageIndex: 0,
};
}
componentDidMount() {
setInterval(() => {
this.setState({
currentPercentageIndex: (this.state.currentPercentageIndex + 1) % this.props.percentages.length
});
}, this.props.interval);
}
getStyles() {
return this.props.stylesForPercentage ? (
this.props.stylesForPercentage(this.getCurrentPercentage())
) : {};
}
getCurrentPercentage() {
return this.props.percentages[this.state.currentPercentageIndex];
}
getText() {
return this.props.textForPercentage
? this.props.textForPercentage(this.getCurrentPercentage())
: `${this.getCurrentPercentage()}%`;
}
render() {
return (
<CircularProgressbar
{...this.props}
percentage={this.getCurrentPercentage()}
text={this.getText()}
styles={this.getStyles()}
/>
);
}
}
ChangingProgressbar.defaultProps = {
interval: 1000,
}
export default ChangingProgressbar;

View File

@ -1,139 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import CircularProgressbar from '../src';
import ChangingProgressbar from './ChangingProgressbar';
const githubURL = 'https://github.com/kevinsqi/react-circular-progressbar';
const Example = ({ description, children }) => (
<div className="col-xs-12 col-sm-6 col-md-3">
<div className="row mb-1">
<div className="col-xs-6 offset-xs-3">{children}</div>
</div>
<p className="text-xs-center">{description}</p>
</div>
);
class Demo extends React.Component {
render() {
return (
<div className="container">
<div className="row mt-3">
<div className="col-xs-12">
<div className="text-xs-center">
<h1 className="mb-2">react-circular-progressbar</h1>
<p>A circular progress indicator component</p>
</div>
</div>
</div>
<div className="row mt-3 mb-3">
<div className="col-xs-6 offset-xs-3 col-md-2 offset-md-5">
<ChangingProgressbar
percentages={[0, 20, 40, 60, 80, 100]}
stylesForPercentage={(percentage) => {
const alpha = (100 + percentage) / 200;
return {
path: {
stroke: `rgba(62, 152, 199, ${alpha})`,
},
};
}}
/>
</div>
</div>
<hr />
<div className="row mt-3">
<h2 className="text-xs-center mb-3">Built with SVG and styled with plain CSS.</h2>
<Example description="Customize text and styling dynamically based on percentage.">
<ChangingProgressbar
percentages={[75, 100]}
classForPercentage={(percentage) => {
return percentage === 100 ? 'complete' : 'incomplete';
}}
textForPercentage={(percentage) => {
return percentage === 100 ? `${percentage}!!` : `${percentage}`;
}}
/>
</Example>
<Example description="Customize stroke width and make it go counterclockwise.">
<ChangingProgressbar percentages={[0, 20, 80]} strokeWidth={5} counterClockwise />
</Example>
<Example description="Add a background color for that inverted look.">
<CircularProgressbar
className="CircularProgressbar-inverted"
background
backgroundPadding={5}
strokeWidth={6}
percentage={66}
text={`${66}%`}
/>
</Example>
<Example description="With SVG and CSS you can do whatever you want.">
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<div style={{ position: 'absolute', width: '100%' }}>
<CircularProgressbar percentage={50} />
</div>
<div style={{ width: '100%', padding: '10%' }}>
<img style={{ width: '100%' }} src="https://i.imgur.com/b9NyUGm.png" alt="doge" />
</div>
</div>
</Example>
</div>
<hr />
<div className="mt-3 mb-3">
<h2 className="text-xs-center">Installation</h2>
<div className="text-xs-center mt-3">
<p>Install with yarn or npm:</p>
<p className="mb-3">
<code>yarn add react-circular-progressbar</code>
</p>
<a className="btn btn-info btn-lg" href={githubURL}>
View docs on Github
</a>
</div>
</div>
<hr />
<div className="mt-3 mb-3">
<h2 className="text-xs-center">Try it out</h2>
<div className="row mt-3">
<div className="col-md-10 offset-md-1">
<iframe
src="https://codesandbox.io/embed/vymm4oln6y"
style={{
width: '100%',
height: '500px',
border: 0,
borderRadius: '4px',
overflow: 'hidden',
}}
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
/>
</div>
</div>
</div>
<hr />
<div className="my-3">
<div className="text-xs-center">
<a className="btn btn-info btn-lg" href={githubURL}>
View docs on Github
</a>
<div className="mt-3">
Built by <a href="http://www.kevinqi.com/">Kevin Qi</a>
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(React.createElement(Demo), document.getElementById('demo'));

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>react-circular-progressbar</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/css/bootstrap.min.css" />
<link rel="stylesheet" href="styles.css" />
<style>
/* bootstrap overrides */
a {
color: #3e98c7;
}
a:hover {
text-decoration: none;
}
/* demo style overrides */
.CircularProgressbar.incomplete .CircularProgressbar-path {
stroke: #f66;
}
.CircularProgressbar.incomplete .CircularProgressbar-text {
fill: #f66;
}
.CircularProgressbar.complete .CircularProgressbar-path {
stroke: #99f;
}
.CircularProgressbar.complete .CircularProgressbar-text {
fill: #99f;
}
</style>
</head>
<body>
<div id="demo"></div>
<script src="index.js" type="text/javascript"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +0,0 @@
/*
* react-circular-progressbar styles
*
* All of the styles in this file are optional and configurable!
*/
.CircularProgressbar {
/*
* This fixes an issue where the CircularProgressbar svg has
* 0 width inside a "display: flex" container, and thus not visible.
*
* If you're not using "display: flex", you can remove this style.
*/
width: 100%;
}
.CircularProgressbar .CircularProgressbar-path {
stroke: #3e98c7;
stroke-linecap: round;
transition: stroke-dashoffset 0.5s ease 0s;
}
.CircularProgressbar .CircularProgressbar-trail {
stroke: #d6d6d6;
}
.CircularProgressbar .CircularProgressbar-text {
fill: #3e98c7;
font-size: 20px;
dominant-baseline: middle;
text-anchor: middle;
}
.CircularProgressbar .CircularProgressbar-background {
fill: #d6d6d6;
}
/*
* Sample background styles. Use these with e.g.:
*
* <CircularProgressbar
* className="CircularProgressbar-inverted"
* background
* percentage={50}
* />
*/
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-background {
fill: #3e98c7;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-text {
fill: #fff;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-path {
stroke: #fff;
}
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-trail {
stroke: transparent;
}

10
jest.config.json Normal file
View File

@ -0,0 +1,10 @@
{
"rootDir": "test",
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"coverageDirectory": "<rootDir>/../coverage",
"setupFilesAfterEnv": ["<rootDir>/setupTests.ts"]
}

View File

@ -3,7 +3,12 @@
"version": "1.0.0",
"description": "A circular progress indicator component",
"author": "Kevin Qi <iqnivek@gmail.com>",
"main": "./dist/index.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"style": "dist/styles.css",
"files": [
"dist"
],
"repository": "https://github.com/kevinsqi/react-circular-progressbar.git",
"license": "MIT",
"keywords": [
@ -12,43 +17,35 @@
"react-component",
"svg"
],
"style": "dist/styles.css",
"files": [
"dist"
],
"scripts": {
"build": "NODE_ENV=production webpack && npm run build:demo && npm run build:css",
"build:css": "mkdir -p dist && cp ./src/styles.css ./dist/styles.css && cp ./src/styles.css ./docs/styles.css",
"build:demo": "NODE_ENV=demo webpack",
"build": "npm-run-all clean build:css build:js",
"build:css": "postcss src/styles.css --use autoprefixer -d dist/ --no-map",
"build:js": "tsc",
"clean": "rimraf dist",
"lint": "eslint src test",
"prepare": "npm run clean && npm run build",
"test": "mocha --compilers js:babel-register --recursive --require ./test/setup.js",
"test:watch": "npm test -- --watch",
"start": "webpack-dev-server --progress --inline"
"format": "prettier --write 'src/**/*' 'demo/src/**/*'",
"prepare": "npm-run-all clean build",
"start": "npm-run-all --parallel start:css start:js",
"start:css": "postcss src/styles.css --use autoprefixer -d dist/ --no-map --watch",
"start:js": "tsc -w",
"test": "jest --config jest.config.json --coverage"
},
"devDependencies": {
"babel-core": "^6.3.15",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.0.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.3.13",
"babel-register": "^6.8.0",
"chai": "^3.5.0",
"enzyme": "^2.3.0",
"eslint": "^3.11.0",
"eslint-config-airbnb": "^14.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.0.0",
"jsdom": "^9.0.0",
"mocha": "^3.3.0",
"react": "^15.4.0",
"react-addons-test-utils": "^15.0.2",
"react-dom": "^15.4.0",
"@types/enzyme": "^3.9.1",
"@types/enzyme-adapter-react-16": "^1.0.5",
"@types/jest": "^24.0.11",
"@types/react": "^16.8.14",
"autoprefixer": "^9.5.1",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.12.1",
"jest": "^24.7.1",
"npm-run-all": "^4.1.5",
"postcss-cli": "^6.1.2",
"prettier": "^1.17.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"rimraf": "^2.3.4",
"webpack": "^2.5.1",
"webpack-dev-server": "^2.4.5"
"ts-jest": "^24.0.2",
"typescript": "^3.4.4"
},
"dependencies": {
"prop-types": "^15.5.10"

View File

@ -9,8 +9,70 @@ const FULL_RADIUS = 50;
const CENTER_X = 50;
const CENTER_Y = 50;
class CircularProgressbar extends React.Component {
constructor(props) {
type CircularProgressbarDefaultProps = {
strokeWidth: number;
className: string;
text: string;
background: boolean;
backgroundPadding: number;
initialAnimation: boolean;
counterClockwise: boolean;
classes: {
root: string;
trail: string;
path: string;
text: string;
background: string;
};
styles: {
root?: object;
trail?: object;
path?: object;
text?: object;
background?: object;
};
};
type CircularProgressbarProps = CircularProgressbarDefaultProps & {
percentage: number;
};
type CircularProgressbarState = {
percentage: number;
};
class CircularProgressbar extends React.Component<
CircularProgressbarProps,
CircularProgressbarState
> {
initialTimeout: number | undefined = undefined;
requestAnimationFrame: number | undefined = undefined;
static defaultProps: CircularProgressbarDefaultProps = {
strokeWidth: 8,
className: '',
text: '',
background: false,
backgroundPadding: 0,
initialAnimation: false,
counterClockwise: false,
classes: {
root: 'CircularProgressbar',
trail: 'CircularProgressbar-trail',
path: 'CircularProgressbar-path',
text: 'CircularProgressbar-text',
background: 'CircularProgressbar-background',
},
styles: {
root: {},
trail: {},
path: {},
text: {},
background: {},
},
};
constructor(props: CircularProgressbarProps) {
super(props);
this.state = {
@ -20,7 +82,7 @@ class CircularProgressbar extends React.Component {
componentDidMount() {
if (this.props.initialAnimation) {
this.initialTimeout = setTimeout(() => {
this.initialTimeout = window.setTimeout(() => {
this.requestAnimationFrame = window.requestAnimationFrame(() => {
this.setState({
percentage: this.props.percentage,
@ -30,7 +92,7 @@ class CircularProgressbar extends React.Component {
}
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps: CircularProgressbarProps) {
this.setState({
percentage: nextProps.percentage,
});
@ -38,7 +100,9 @@ class CircularProgressbar extends React.Component {
componentWillUnmount() {
clearTimeout(this.initialTimeout);
window.cancelAnimationFrame(this.requestAnimationFrame);
if (this.requestAnimationFrame) {
window.cancelAnimationFrame(this.requestAnimationFrame);
}
}
getBackgroundPadding() {
@ -72,7 +136,10 @@ class CircularProgressbar extends React.Component {
getPathStyles() {
const diameter = Math.PI * 2 * this.getPathRadius();
const truncatedPercentage = Math.min(Math.max(this.state.percentage, MIN_PERCENTAGE), MAX_PERCENTAGE);
const truncatedPercentage = Math.min(
Math.max(this.state.percentage, MIN_PERCENTAGE),
MAX_PERCENTAGE,
);
const dashoffset = ((100 - truncatedPercentage) / 100) * diameter;
return {
@ -84,18 +151,11 @@ class CircularProgressbar extends React.Component {
getPathRadius() {
// the radius of the path is defined to be in the middle, so in order for the path to
// fit perfectly inside the 100x100 viewBox, need to subtract half the strokeWidth
return FULL_RADIUS - (this.props.strokeWidth / 2) - this.getBackgroundPadding();
return FULL_RADIUS - this.props.strokeWidth / 2 - this.getBackgroundPadding();
}
render() {
const {
percentage,
className,
classes,
styles,
strokeWidth,
text,
} = this.props;
const { percentage, className, classes, styles, strokeWidth, text } = this.props;
const pathDescription = this.getPathDescription();
return (
@ -104,17 +164,15 @@ class CircularProgressbar extends React.Component {
style={styles.root}
viewBox={`0 0 ${MAX_X} ${MAX_Y}`}
>
{
this.props.background ? (
<circle
className={classes.background}
style={styles.background}
cx={CENTER_X}
cy={CENTER_Y}
r={FULL_RADIUS}
/>
) : null
}
{this.props.background ? (
<circle
className={classes.background}
style={styles.background}
cx={CENTER_X}
cy={CENTER_Y}
r={FULL_RADIUS}
/>
) : null}
<path
className={classes.trail}
@ -132,58 +190,14 @@ class CircularProgressbar extends React.Component {
style={Object.assign({}, styles.path, this.getPathStyles())}
/>
{
text ? (
<text
className={classes.text}
style={styles.text}
x={CENTER_X}
y={CENTER_Y}
>
{text}
</text>
) : null
}
{text ? (
<text className={classes.text} style={styles.text} x={CENTER_X} y={CENTER_Y}>
{text}
</text>
) : null}
</svg>
);
}
}
CircularProgressbar.propTypes = {
percentage: PropTypes.number.isRequired,
className: PropTypes.string,
text: PropTypes.string,
classes: PropTypes.objectOf(PropTypes.string),
styles: PropTypes.objectOf(PropTypes.object),
strokeWidth: PropTypes.number,
background: PropTypes.bool,
backgroundPadding: PropTypes.number,
initialAnimation: PropTypes.bool,
counterClockwise: PropTypes.bool,
};
CircularProgressbar.defaultProps = {
strokeWidth: 8,
className: '',
text: null,
classes: {
root: 'CircularProgressbar',
trail: 'CircularProgressbar-trail',
path: 'CircularProgressbar-path',
text: 'CircularProgressbar-text',
background: 'CircularProgressbar-background',
},
styles: {
root: {},
trail: {},
path: {},
text: {},
background: {},
},
background: false,
backgroundPadding: null,
initialAnimation: false,
counterClockwise: false,
};
export default CircularProgressbar;

View File

@ -0,0 +1,141 @@
import React from 'react';
import { shallow } from 'enzyme';
import CircularProgressbar from '../src/index';
describe('<CircularProgressbar />', () => {
test('SVG rendered to DOM', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} />);
expect(wrapper.find('svg').length).toBe(1);
});
describe('props.strokeWidth', () => {
test('Applies to path', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} strokeWidth={2} />);
expect(wrapper.find('.CircularProgressbar-path').prop('strokeWidth')).toEqual(2);
});
});
describe('props.className', () => {
test('Applies to SVG', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} className="my-custom-class" />);
expect(wrapper.find('svg').prop('className')).toContain('my-custom-class');
});
});
describe('props.text', () => {
test('Does not render when blank', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} />);
expect(wrapper.find('.CircularProgressbar-text').exists()).toEqual(false);
});
test('Renders the correct string', () => {
const percentage = 50;
const wrapper = shallow(
<CircularProgressbar percentage={percentage} text={`${percentage}%`} />,
);
expect(wrapper.find('.CircularProgressbar-text').text()).toEqual('50%');
});
});
describe('props.percentage', () => {
test('Renders correct path', () => {
const percentage = 30;
const wrapper = shallow(
<CircularProgressbar percentage={percentage} strokeWidth={0} className="my-custom-class" />,
);
const dashoffset = wrapper.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset;
const expectedRadius = 50;
const expectedDiameter = 2 * expectedRadius * Math.PI;
const expectedOffset = ((100 - percentage) / 100) * expectedDiameter;
expect(dashoffset).toEqual(`${expectedOffset}px`);
const expectedArcto = `a ${expectedRadius},${expectedRadius}`;
expect(wrapper.find('.CircularProgressbar-path').prop('d')).toContain(expectedArcto);
});
});
describe('props.counterClockwise', () => {
test('Reverses dashoffset', () => {
const clockwise = shallow(<CircularProgressbar percentage={50} />);
const counterClockwise = shallow(<CircularProgressbar percentage={50} counterClockwise />);
// Counterclockwise should have the negative dashoffset of clockwise
expect(
`-${clockwise.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset}`,
).toEqual(counterClockwise.find('.CircularProgressbar-path').prop('style')!.strokeDashoffset);
});
});
describe('props.styles', () => {
test('Style customizations applied to all subcomponents', () => {
const percentage = 50;
const wrapper = shallow(
<CircularProgressbar
percentage={percentage}
text={`${percentage}%`}
background
styles={{
root: { stroke: '#000000' },
trail: { stroke: '#111111' },
path: { stroke: '#222222' },
text: { stroke: '#333333' },
background: { stroke: '#444444' },
}}
/>,
);
expect(wrapper.find('.CircularProgressbar').prop('style')!.stroke).toEqual('#000000');
expect(wrapper.find('.CircularProgressbar-trail').prop('style')!.stroke).toEqual('#111111');
expect(wrapper.find('.CircularProgressbar-path').prop('style')!.stroke).toEqual('#222222');
expect(wrapper.find('.CircularProgressbar-text').prop('style')!.stroke).toEqual('#333333');
expect(wrapper.find('.CircularProgressbar-background').prop('style')!.stroke).toEqual(
'#444444',
);
});
});
describe('props.background', () => {
test('Background does not render when prop is false', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} background={false} />);
expect(wrapper.find('.CircularProgressbar-background').exists()).toEqual(false);
});
test('Renders a <circle> with correct radius', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} background />);
expect(wrapper.find('.CircularProgressbar-background').exists()).toBe(true);
expect(wrapper.find('.CircularProgressbar-background').type()).toEqual('circle');
expect(wrapper.find('.CircularProgressbar-background').prop('r')).toEqual(50);
});
});
describe('props.classes', () => {
test('Has default values', () => {
const wrapper = shallow(<CircularProgressbar percentage={50} text="50" />);
expect(wrapper.find('.CircularProgressbar').type()).toEqual('svg');
expect(wrapper.find('.CircularProgressbar-path').type()).toEqual('path');
expect(wrapper.find('.CircularProgressbar-trail').type()).toEqual('path');
expect(wrapper.find('.CircularProgressbar-text').type()).toEqual('text');
});
test('Prop overrides work', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
text="50"
background
classes={{
root: 'someRootClass',
path: 'somePathClass',
trail: 'someTrailClass',
text: 'someTextClass',
background: 'someBackgroundClass',
}}
/>,
);
// Assert default classes don't exist
expect(wrapper.find('.CircularProgressbar').exists()).toBe(false);
expect(wrapper.find('.CircularProgressbar-path').exists()).toBe(false);
expect(wrapper.find('.CircularProgressbar-trail').exists()).toBe(false);
expect(wrapper.find('.CircularProgressbar-text').exists()).toBe(false);
expect(wrapper.find('.CircularProgressbar-background').exists()).toBe(false);
// Assert override classes do exist
expect(wrapper.find('.someRootClass').type()).toEqual('svg');
expect(wrapper.find('.somePathClass').type()).toEqual('path');
expect(wrapper.find('.someTrailClass').type()).toEqual('path');
expect(wrapper.find('.someTextClass').type()).toEqual('text');
expect(wrapper.find('.someBackgroundClass').type()).toEqual('circle');
});
});
});

View File

@ -1,197 +0,0 @@
import React from 'react';
import { assert } from 'chai';
import { shallow } from 'enzyme';
import CircularProgressbar from '../../src';
describe('CircularProgressbar', () => {
it('should not throw exceptions in base case', () => {
assert.doesNotThrow(() => <CircularProgressbar percentage={50} />);
});
it('should render as an svg', () => {
const wrapper = shallow(
<CircularProgressbar percentage={50} />
);
assert.equal(1, wrapper.find('svg').length);
});
});
describe('CircularProgressbar props', () => {
it('strokeWidth', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
strokeWidth={2}
/>
);
assert.equal(2, wrapper.find('.CircularProgressbar-path').prop('strokeWidth'));
});
it('className', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
className="my-custom-class"
/>
);
assert(wrapper.find('svg').prop('className').includes('my-custom-class'));
});
it('text does not render when null', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
/>
);
assert(!wrapper.find('.CircularProgressbar-text').exists());
});
it('text', () => {
const percentage = 50;
const wrapper = shallow(
<CircularProgressbar
percentage={percentage}
text={`${percentage}%`}
/>
);
assert.equal(wrapper.find('.CircularProgressbar-text').text(), '50%');
});
it('percentage', () => {
const percentage = 30;
const wrapper = shallow(
<CircularProgressbar
percentage={percentage}
strokeWidth={0}
className="my-custom-class"
/>
);
const dashoffset = wrapper.find('.CircularProgressbar-path').prop('style').strokeDashoffset;
const expectedRadius = 50;
const expectedDiameter = 2 * expectedRadius * Math.PI;
const expectedOffset = ((100 - percentage) / 100) * expectedDiameter;
assert.equal(dashoffset, `${expectedOffset}px`);
const expectedArcto = `a ${expectedRadius},${expectedRadius}`;
assert(wrapper.find('.CircularProgressbar-path').prop('d').includes(expectedArcto));
});
it('counterClockwise', () => {
const clockwise = shallow(
<CircularProgressbar percentage={50} />
);
const counterClockwise = shallow(
<CircularProgressbar percentage={50} counterClockwise />
);
assert.equal(
`-${clockwise.find('.CircularProgressbar-path').prop('style').strokeDashoffset}`,
counterClockwise.find('.CircularProgressbar-path').prop('style').strokeDashoffset,
'counterclockwise should have the negative dashoffset of clockwise',
);
});
it('styles', () => {
const percentage = 50;
const wrapper = shallow(
<CircularProgressbar
percentage={percentage}
text={`${percentage}%`}
background
styles={{
root: { stroke: '#000000' },
trail: { stroke: '#111111' },
path: { stroke: '#222222' },
text: { stroke: '#333333' },
background: { stroke: '#444444' },
}}
/>
);
assert.equal(
wrapper.find('.CircularProgressbar').prop('style').stroke,
'#000000',
);
assert.equal(
wrapper.find('.CircularProgressbar-trail').prop('style').stroke,
'#111111',
);
assert.equal(
wrapper.find('.CircularProgressbar-path').prop('style').stroke,
'#222222',
);
assert.equal(
wrapper.find('.CircularProgressbar-text').prop('style').stroke,
'#333333',
);
assert.equal(
wrapper.find('.CircularProgressbar-background').prop('style').stroke,
'#444444',
);
});
it('background does not render when null', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
/>
);
assert(!wrapper.find('.CircularProgressbar-background').exists());
});
it('background', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
background
/>
);
assert(wrapper.find('.CircularProgressbar-background').exists());
assert.equal(wrapper.find('.CircularProgressbar-background').type(), 'circle');
assert.equal(wrapper.find('.CircularProgressbar-background').prop('r'), 50);
});
it('classes defaults', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
text="50"
/>
);
assert.equal(wrapper.find('.CircularProgressbar').type(), 'svg');
assert.equal(wrapper.find('.CircularProgressbar-path').type(), 'path');
assert.equal(wrapper.find('.CircularProgressbar-trail').type(), 'path');
assert.equal(wrapper.find('.CircularProgressbar-text').type(), 'text');
});
it('classes', () => {
const wrapper = shallow(
<CircularProgressbar
percentage={50}
text="50"
background
classes={{
root: 'someRootClass',
path: 'somePathClass',
trail: 'someTrailClass',
text: 'someTextClass',
background: 'someBackgroundClass',
}}
/>
);
// Assert default classes don't exist
assert(!wrapper.find('.CircularProgressbar').exists());
assert(!wrapper.find('.CircularProgressbar-path').exists());
assert(!wrapper.find('.CircularProgressbar-trail').exists());
assert(!wrapper.find('.CircularProgressbar-text').exists());
assert(!wrapper.find('.CircularProgressbar-background').exists());
// Assert override classes do exist
assert.equal(wrapper.find('.someRootClass').type(), 'svg');
assert.equal(wrapper.find('.somePathClass').type(), 'path');
assert.equal(wrapper.find('.someTrailClass').type(), 'path');
assert.equal(wrapper.find('.someTextClass').type(), 'text');
assert.equal(wrapper.find('.someBackgroundClass').type(), 'circle');
});
});

View File

@ -1,20 +0,0 @@
/* global document */
// https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md#using-enzyme-with-jsdom
const jsdom = require('jsdom').jsdom;
const exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js',
};

4
test/setupTests.ts Normal file
View File

@ -0,0 +1,4 @@
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"declaration": true,
"declarationDir": "dist",
"esModuleInterop": true,
"jsx": "react",
"lib": ["es2015", "dom"],
"module": "commonjs",
"noImplicitAny": true,
"outDir": "./dist",
"removeComments": true,
"sourceMap": true,
"strict": true,
"target": "es5"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View File

@ -1,72 +0,0 @@
const webpack = require('webpack');
const path = require('path');
const nodeEnv = process.env.NODE_ENV || 'development';
const demoDir = 'docs';
const webpackConfig = {
context: __dirname,
entry: {
'react-circular-progressbar': [
path.resolve(__dirname, 'src', 'index.jsx')
]
},
output: {
path: path.resolve(__dirname),
filename: 'index.js',
library: 'CircularProgressbar',
libraryTarget: 'umd'
},
resolve: {
extensions: ['.js', '.jsx'],
modules: ['node_modules']
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
loader: 'babel-loader'
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(nodeEnv)
}),
new webpack.LoaderOptionsPlugin({
debug: true
})
]
};
if (nodeEnv === 'development') {
webpackConfig.devtool = 'source-map';
webpackConfig.devServer = { contentBase: path.resolve(__dirname, demoDir) };
webpackConfig.entry['react-circular-progressbar'].unshift('webpack-dev-server/client?http://0.0.0.0:8080/');
webpackConfig.entry['react-circular-progressbar'].push(path.resolve(__dirname, demoDir, 'demo.jsx'));
webpackConfig.output.publicPath = '/';
}
if (nodeEnv === 'demo') {
webpackConfig.entry['react-circular-progressbar'].push(path.resolve(__dirname, demoDir, 'demo.jsx'));
webpackConfig.output.path = path.resolve(__dirname, demoDir);
}
if (nodeEnv === 'production') {
webpackConfig.externals = {
'react': {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
}
};
webpackConfig.output.path = path.resolve(__dirname, 'dist');
webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
sourceMap: false
}));
}
module.exports = webpackConfig;

7051
yarn.lock

File diff suppressed because it is too large Load Diff