├── .babelrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── example ├── .env ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── App.js │ ├── index.js │ └── serviceWorker.js ├── material-ui-currency-field.gif ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── components │ ├── CurrencyTextField │ │ ├── CurrencyTextField.js │ │ ├── CurrencyTextField.md │ │ └── index.js │ └── readme.md └── index.js ├── styleguide.config.js └── styleguide.styles.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-transform-react-jsx", 8 | "@babel/plugin-proposal-object-rest-spread" 9 | ] 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store/ 2 | *~ 3 | .vscode 4 | node_modules 5 | example/node_modules 6 | dist 7 | styleguide 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | example/node_modules 4 | styleguide 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "endOfLine": "lf", 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material-ui currency textfield 2 | [![npm version](https://badge.fury.io/js/%40unicef%2Fmaterial-ui-currency-textfield.svg)](https://badge.fury.io/js/%40unicef%2Fmaterial-ui-currency-textfield) 3 | 4 | `CurrencyTextField` is a [Material-ui](https://material-ui.com/) [react](https://reactjs.org/) component. It provides a user friendly experience while inputing currency numbers. 5 | 6 | `CurrencyTextField` wraps the functionality of autonumeric and it is a port of react-numeric in Material-ui. 7 | 8 | ![Example of material](https://raw.githubusercontent.com/unicef/material-ui-currency-textfield/master/material-ui-currency-field.gif) 9 | 10 | Main features: 11 | * Adds thousands separator automatically. 12 | * Adds automatically the decimals on blur. 13 | * Smart input. User can only type the accepted characters depending on the current value. 14 | * Lots of config options... 15 | 16 | ## Install 17 | 18 | ```bash 19 | npm install @unicef/material-ui-currency-textfield --save 20 | ``` 21 | 22 | ## Usage 23 | 24 | **[Documentation and live demo is available here](https://unicef.github.io/material-ui-currency-textfield/)** 25 | 26 | 27 | ```jsx 28 | import React from 'react' 29 | import CurrencyTextField from '@unicef/material-ui-currency-textfield' 30 | 31 | export default function MyComponent() { 32 | 33 | const [value, setValue] = React.useState(); 34 | 35 | return ( 36 | setValue(value)} 46 | /> 47 | ); 48 | } 49 | ``` 50 | 51 | 52 | ## Development 53 | 54 | In order to extend the component, clone the project and install the dependencies. 55 | ```bash 56 | $ git clone https://github.com/unicef/material-ui-currency-textfield.git 57 | $ npm install 58 | ``` 59 | 60 | The following commands are available: 61 | 62 | ### `npm start` 63 | 64 | Builds the component outputing it in the `dist` folder. It is refreshed everytime you make changes in the code. 65 | 66 | ```bash 67 | npm start 68 | ``` 69 | 70 | To see the output in the browser run the example app ([/example](https://github.com/unicef/material-ui-currency-textfield/tree/master/example)) 71 | 72 | ```bash 73 | cd example 74 | npm install (only first time) 75 | npm start 76 | ``` 77 | Runs the app in the development mode. Open [http://localhost:3000](http://localhost:3000) to view the app in the browser. 78 | 79 | It will reload automatically upon edits. Lint errors are also displayed on the console. 80 | 81 | ### `npm run build` 82 | 83 | Outputs the build for production to the `dist` folder. 84 | 85 | ### `npm run styleguide` 86 | Generates the documentation available on. 87 | 88 | Open [http://localhost:6060](http://localhost:6060) to view it in the browser. 89 | 90 | It watches for changes and automatically reloads the browser. 91 | 92 | We use [styleguidelist](https://react-styleguidist.js.org/) for documenting our custom components. 93 | 94 | ### `npm run styleguide:build` 95 | Builds the styleguide documentation for production. The output targets the `styleguide` folder. 96 | 97 | 98 | ## About UNICEF 99 | 100 | [UNICEF](https://www.unicef.org/) works in over 190 countries and territories to protect the rights of every child. UNICEF has spent more than 70 years working to improve the lives of children and their families. In UNICEF, we **believe all children have a right to survive, thrive and fulfill their potential – to the benefit of a better world**. 101 | 102 | [Donate](https://donate.unicef.org/donate/now) 103 | 104 | 105 | ## Collaborations and support 106 | 107 | Just fork the project and make a pull request. You may also [consider donating](https://donate.unicef.org/donate/now). 108 | 109 | 110 | ## License 111 | 112 | Copyright (c) 2019 UNICEF.org 113 | 114 | Permission is hereby granted, free of charge, to any person obtaining a copy 115 | of this software and associated documentation files (the "Software"), to deal 116 | in the Software without restriction, including without limitation the rights 117 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 118 | copies of the Software, and to permit persons to whom the Software is 119 | furnished to do so, subject to the following conditions: 120 | 121 | The above copyright notice and this permission notice shall be included in all 122 | copies or substantial portions of the Software. 123 | 124 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 125 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 126 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 127 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 128 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 129 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 130 | SOFTWARE. 131 | 132 | 133 | ### Acknowledgements 134 | 135 | The majority of the source code of this repo was developed by [@sureshsevarthi](http://github.com/sureshsevarthi). 136 | 137 | Also, this source code is based on [react-numeric](https://github.com/mkg0/react-numeric). 138 | 139 | -------------------------------------------------------------------------------- /example/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@unicef/material-ui-currency-textfield-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": ".", 6 | "dependencies": { 7 | "@material-ui/core": "^4.1.1", 8 | "@material-ui/icons": "^4.2.0", 9 | "@unicef/material-ui-currency-textfield": "file:..", 10 | "gh-pages": "^2.0.1", 11 | "prop-types": "^15.7.2", 12 | "react": "file:../node_modules/react", 13 | "react-dom": "file:../node_modules/react-dom", 14 | "react-router-dom": "^5.0.1", 15 | "react-scripts": "^3.3.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "predeploy": "npm run build", 21 | "deploy": "gh-pages -d build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": {} 41 | } 42 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/material-ui-currency-textfield/c46ffb12542ca1ce6bd728942bfe8f9decafd0e3/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 26 | UNICEF Material UI currency text field 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import CurrencyTextField from "@unicef/material-ui-currency-textfield" 3 | import { Button } from "@material-ui/core" 4 | 5 | export default function App() { 6 | const [value, setValue] = React.useState(99) 7 | const error = value < 100 8 | 9 | function resetValue() { 10 | setValue(0) 11 | } 12 | 13 | return ( 14 | 15 | setValue(value)} 22 | error={error} 23 | helperText={"minimum number is 100"} 24 | decimalCharacter="." 25 | digitGroupSeparator="," 26 | /> 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from 'react-router-dom' 3 | import ReactDOM from 'react-dom'; 4 | import App from './App' 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /example/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /material-ui-currency-field.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unicef/material-ui-currency-textfield/c46ffb12542ca1ce6bd728942bfe8f9decafd0e3/material-ui-currency-field.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@unicef/material-ui-currency-textfield", 3 | "version": "0.8.6", 4 | "description": "Currency input textfield for react with Material-ui style", 5 | "keywords": [ 6 | "material design", 7 | "material ui", 8 | "currency", 9 | "currency input", 10 | "currency textfield", 11 | "react" 12 | ], 13 | "main": "./dist/index.js", 14 | "typings": "./dist/index.d.ts", 15 | "homepage": "https://unicef.github.io/material-ui-currency-textfield", 16 | "scripts": { 17 | "build": "rollup -c", 18 | "start": "rollup -c -w", 19 | "styleguide": "styleguidist server", 20 | "styleguide:build": "styleguidist build", 21 | "deploy": "gh-pages -d styleguide", 22 | "transpile": "./node_modules/@babel/cli/bin/babel.js src/components/CurrencyTextField -d dist --copy-files", 23 | "prepublishOnly": "npm run transpile", 24 | "publish-demo": "npm run build && npm run deploy" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/unicef/material-ui-currency-textfield.git" 29 | }, 30 | "author": "UNICEF", 31 | "license": "MIT", 32 | "peerDependencies": { 33 | "react": "^16.8.6", 34 | "react-dom": "^16.8.6", 35 | "prop-types": "^15.7.2", 36 | "@material-ui/core": ">= 4.3.0" 37 | }, 38 | "devDependencies": { 39 | "@babel/cli": "^7.5.5", 40 | "@babel/core": "^7.5.5", 41 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5", 42 | "@babel/plugin-transform-react-jsx": "^7.3.0", 43 | "@babel/preset-env": "^7.5.5", 44 | "@babel/preset-react": "^7.0.0", 45 | "@material-ui/core": "^4.8.3", 46 | "@types/node": "^12.6.8", 47 | "@types/react": "^16.9.22", 48 | "babel-core": "^6.23.0", 49 | "babel-loader": "^8.0.5", 50 | "babel-plugin-external-helpers": "^6.22.0", 51 | "babel-plugin-transform-react-jsx": "^6.24.1", 52 | "babel-preset-env": "^1.7.0", 53 | "babel-preset-react": "^6.24.1", 54 | "babel-preset-stage-0": "^6.24.1", 55 | "babelrc-rollup": "^3.0.0", 56 | "core-util-is": "^1.0.2", 57 | "file-loader": "^4.1.0", 58 | "gh-pages": "^2.1.1", 59 | "node-sass": "^4.12.0", 60 | "prop-types": "^15.7.2", 61 | "react": "^16.8.6", 62 | "react-dom": "^16.8.6", 63 | "react-router-dom": "^5.0.1", 64 | "react-select": "^3.0.4", 65 | "react-styleguidist": "^10.4.2", 66 | "rollup": "^1.17.0", 67 | "rollup-plugin-babel": "^3.0.7", 68 | "rollup-plugin-commonjs": "^10.0.1", 69 | "rollup-plugin-node-resolve": "^5.2.0", 70 | "rollup-plugin-peer-deps-external": "^2.2.0", 71 | "rollup-plugin-url": "^2.2.2", 72 | "style-loader": "^0.23.1", 73 | "webpack": "^4.29.6", 74 | "webpack-cli": "^3.3.6", 75 | "webpack-dev-server": "^3.2.1" 76 | }, 77 | "dependencies": { 78 | "autonumeric": "^4.5.4" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | import external from 'rollup-plugin-peer-deps-external' 4 | import resolve from 'rollup-plugin-node-resolve' 5 | import url from 'rollup-plugin-url' 6 | 7 | export default { 8 | input: 'src/index.js', 9 | output: [ 10 | { 11 | file: 'dist/index.js', 12 | format: 'cjs' 13 | } 14 | ], 15 | plugins: [ 16 | external(), 17 | url(), 18 | babel({ 19 | babelrc: false, 20 | presets: [ 21 | ["env", { 22 | "modules": false 23 | }], 24 | "stage-0", 25 | "react" 26 | ], 27 | exclude: 'node_modules/**', 28 | plugins: [ 'external-helpers' ] 29 | }), 30 | resolve(), 31 | commonjs({ 32 | include: 'node_modules/**', 33 | namedExports: { 34 | 'node_modules/react/index.js': [ 35 | 'cloneElement', 36 | 'createContext', 37 | 'Component', 38 | 'createElement' 39 | ], 40 | 'node_modules/react-dom/index.js': ['render', 'hydrate'], 41 | 'node_modules/react-is/index.js': [ 42 | 'isElement', 43 | 'isValidElementType', 44 | 'ForwardRef' 45 | ] 46 | } 47 | }) 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /src/components/CurrencyTextField/CurrencyTextField.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import PropTypes from "prop-types" 3 | import AutoNumeric from "autonumeric" 4 | import { withStyles } from "@material-ui/styles" 5 | import { TextField, InputAdornment } from "@material-ui/core" 6 | 7 | const styles = theme => ({ 8 | textField: props => ({ 9 | textAlign: props.textAlign || "right", 10 | }), 11 | }) 12 | 13 | /** 14 | * CurrencyTextField is a [react](https://reactjs.org/) component with automated currency and number format, and with [Material-ui](https://material-ui.com/) look and feel. 15 | * 16 | * CurrencyTextField is a wrapper component for autonumeric and based on react-numeric. 17 | * 18 | * Main features: 19 | * * Adds thousands separator automatically. 20 | * * Adds automatically the decimals on blur. 21 | * * Smart input. User can only type the accepted characters depending on the current value. 22 | * * Lots of config options... 23 | * * It accepts all the `props` and `classes` of Material-Ui TextField API (Ex: classes, label, helperText, variant). 24 | * * And also all the `options` from AutoNumeric 25 | */ 26 | 27 | class CurrencyTextField extends React.Component { 28 | constructor(props) { 29 | super(props) 30 | this.getValue = this.getValue.bind(this) 31 | this.callEventHandler = this.callEventHandler.bind(this) 32 | } 33 | 34 | componentDidMount() { 35 | const { currencySymbol, ...others } = this.props 36 | this.autonumeric = new AutoNumeric(this.input, this.props.value, { 37 | ...this.props.preDefined, 38 | ...others, 39 | onChange: undefined, 40 | onFocus: undefined, 41 | onBlur: undefined, 42 | onKeyPress: undefined, 43 | onKeyUp: undefined, 44 | onKeyDown: undefined, 45 | watchExternalChanges: false, 46 | }) 47 | } 48 | componentWillUnmount() { 49 | this.autonumeric.remove() 50 | } 51 | 52 | componentWillReceiveProps(newProps) { 53 | const isValueChanged = 54 | this.props.value !== newProps.value && this.getValue() !== newProps.value 55 | 56 | if (isValueChanged) { 57 | this.autonumeric.set(newProps.value) 58 | } 59 | } 60 | 61 | getValue() { 62 | if (!this.autonumeric) return 63 | const valueMapper = { 64 | string: numeric => numeric.getNumericString(), 65 | number: numeric => numeric.getNumber(), 66 | } 67 | return valueMapper[this.props.outputFormat](this.autonumeric) 68 | } 69 | callEventHandler(event, eventName) { 70 | if (!this.props[eventName]) return 71 | this.props[eventName](event, this.getValue()) 72 | } 73 | render() { 74 | const { 75 | classes, 76 | currencySymbol, 77 | inputProps, 78 | InputProps, 79 | ...others 80 | } = this.props 81 | 82 | const otherProps = {} 83 | ;[ 84 | "id", 85 | "label", 86 | "className", 87 | "autoFocus", 88 | "variant", 89 | "style", 90 | "error", 91 | "disabled", 92 | "type", 93 | "name", 94 | "defaultValue", 95 | "tabIndex", 96 | "fullWidth", 97 | "rows", 98 | "rowsMax", 99 | "select", 100 | "required", 101 | "helperText", 102 | "unselectable", 103 | "margin", 104 | "SelectProps", 105 | "multiline", 106 | "size", 107 | "FormHelperTextProps", 108 | "placeholder", 109 | ].forEach(prop => (otherProps[prop] = this.props[prop])) 110 | 111 | return ( 112 | (this.input = ref)} 114 | onChange={e => this.callEventHandler(e, "onChange")} 115 | onFocus={e => this.callEventHandler(e, "onFocus")} 116 | onBlur={e => this.callEventHandler(e, "onBlur")} 117 | onKeyPress={e => this.callEventHandler(e, "onKeyPress")} 118 | onKeyUp={e => this.callEventHandler(e, "onKeyUp")} 119 | onKeyDown={e => this.callEventHandler(e, "onKeyDown")} 120 | InputProps={{ 121 | startAdornment: ( 122 | {currencySymbol} 123 | ), 124 | ...InputProps, 125 | }} 126 | inputProps={{ 127 | className: classes.textField, 128 | ...inputProps, 129 | }} 130 | {...otherProps} 131 | /> 132 | ) 133 | } 134 | } 135 | 136 | CurrencyTextField.propTypes = { 137 | type: PropTypes.oneOf(["text", "tel", "hidden"]), 138 | /** The variant to use. */ 139 | variant: PropTypes.string, 140 | id: PropTypes.string, 141 | /** The CSS class name of the wrapper element. */ 142 | className: PropTypes.string, 143 | /** Inline styling for element */ 144 | style: PropTypes.object, 145 | /** If true, the input element will be disabled. */ 146 | disabled: PropTypes.bool, 147 | /** The label content. */ 148 | label: PropTypes.string, 149 | /** Align the numbers in the textField. 150 | * If you pass the `inputProps` from TextFieldAPI text align won't work. 151 | * then, you have handle it by className with your own class inside inputProps. 152 | */ 153 | textAlign: PropTypes.oneOf(["right", "left", "center"]), 154 | /** Tab index for the element */ 155 | tabIndex: PropTypes.number, 156 | /** If true, the input element will be focused during the first mount. */ 157 | autoFocus: PropTypes.bool, 158 | /** The short hint displayed in the input before the user enters a value. */ 159 | placeholder: PropTypes.string, 160 | /** value to be enter and display in input */ 161 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 162 | /** Callback fired when the value is changed. */ 163 | onChange: PropTypes.func, 164 | /** Callback fired when focused on element. */ 165 | onFocus: PropTypes.func, 166 | /** Callback fired on blur. */ 167 | onBlur: PropTypes.func, 168 | /** Callback fired on key press. */ 169 | onKeyPress: PropTypes.func, 170 | /** Callback fired on key press. */ 171 | onKeyUp: PropTypes.func, 172 | /** Callback fired on key press. */ 173 | onKeyDown: PropTypes.func, 174 | /** Defines the currency symbol string. */ 175 | currencySymbol: PropTypes.string, 176 | /** Defines what decimal separator character is used. */ 177 | decimalCharacter: PropTypes.string, 178 | /** Allow to declare an alternative decimal separator which is automatically replaced by `decimalCharacter` when typed. */ 179 | decimalCharacterAlternative: PropTypes.string, 180 | /** Defines the default number of decimal places to show on the formatted value. */ 181 | decimalPlaces: PropTypes.number, 182 | /** Defines how many decimal places should be visible when the element is unfocused null. */ 183 | decimalPlacesShownOnBlur: PropTypes.number, 184 | /** Defines how many decimal places should be visible when the element has the focus. */ 185 | decimalPlacesShownOnFocus: PropTypes.number, 186 | /** Defines the thousand grouping separator character */ 187 | digitGroupSeparator: PropTypes.string, 188 | /** Controls the leading zero behavior   */ 189 | leadingZero: PropTypes.oneOf(["allow", "deny", "keep"]), 190 | /** maximum value that can be enter */ 191 | maximumValue: PropTypes.string, 192 | /** minimum value that can be enter */ 193 | minimumValue: PropTypes.string, 194 | /** placement of the negitive and possitive sign symbols */ 195 | negativePositiveSignPlacement: PropTypes.oneOf(["l", "r", "p", "s"]), 196 | /** Defines the negative sign symbol to use  */ 197 | negativeSignCharacter: PropTypes.string, 198 | /** how the value should be formatted,before storing it */ 199 | outputFormat: PropTypes.oneOf(["string", "number"]), 200 | /** Defines if the element value should be selected on focus. */ 201 | selectOnFocus: PropTypes.bool, 202 | /** Defines the positive sign symbol to use. */ 203 | positiveSignCharacter: PropTypes.string, 204 | /** Defines if the element should be set as read only on initialization. */ 205 | readOnly: PropTypes.bool, 206 | /** predefined objects are available in AutoNumeric*/ 207 | preDefined: PropTypes.object, 208 | } 209 | 210 | CurrencyTextField.defaultProps = { 211 | type: "text", 212 | variant: "standard", 213 | currencySymbol: "$", 214 | outputFormat: "number", 215 | textAlign: "right", 216 | maximumValue: "10000000000000", 217 | minimumValue: "-10000000000000", 218 | } 219 | export default withStyles(styles)(CurrencyTextField) 220 | 221 | export const predefinedOptions = AutoNumeric.getPredefinedOptions() 222 | -------------------------------------------------------------------------------- /src/components/CurrencyTextField/CurrencyTextField.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | #### Type of variants: 4 | 5 | ```html 6 | variant = 'standard' (default) 7 | | 'outlined' 8 | | 'filled' 9 | ``` 10 | 11 | ```jsx 12 | import { Grid, Typography } from '@material-ui/core' 13 | 14 | const [value, setValue] = React.useState(100); 15 | 16 | 17 | 18 | Standard 19 | setValue(value)} 25 | /> 26 | 27 | 28 | Outlined 29 | setValue(value)} 35 | /> 36 | 37 | 38 | Filled 39 | setValue(value)} 45 | /> 46 | 47 | 48 | ``` 49 | #### Currency symbol 50 | ```html 51 | currencySymbol = 'string' 52 | 53 | Ex : '$' | '£' | '€' 54 | ``` 55 | ```jsx 56 | import { Grid, Typography } from '@material-ui/core' 57 | 58 | const [value, setValue] = React.useState(100); 59 | 60 | 61 | 62 | Euro € 63 | setValue(value)} 70 | /> 71 | 72 | 73 | GBP £ 74 | setValue(value)} 80 | /> 81 | 82 | 83 | USD $ 84 | setValue(value)} 90 | /> 91 | 92 | 93 | ``` 94 | #### Change in characters 95 | ```html 96 | decimalCharacter="," 97 | digitGroupSeparator="." 98 | ``` 99 | ```jsx 100 | const [value, setValue] = React.useState(100); 101 | 102 | setValue(value)} 109 | /> 110 | ``` 111 | 112 | #### Output format : 113 | output value to be stored as string or number. 114 | ```html 115 | outputFormat='number' (default) 116 | | 'string' 117 | ``` 118 | ```jsx 119 | const [value, setValue] = React.useState(100); 120 | 121 | setValue(value)} 127 | /> 128 | ``` 129 | 130 | #### error and helperText 131 | ```html 132 | error={bool} 133 | helperText='string' 134 | ``` 135 | ```jsx 136 | const [value, setValue] = React.useState(100); 137 | const isValid = value < 1000; 138 | 139 | setValue(value)} 144 | error={isValid} 145 | helperText={isValid && "minimum amount is 1000"} 146 | decimalCharacter="." 147 | digitGroupSeparator="," 148 | />; 149 | ``` 150 | #### maximumValue and minimumValue 151 | ```html 152 | maximumValue={10000000000000} (default) | "Value can be increased and decreased" 153 | minimumValue={-10000000000000} (default) | "Value can be increased and decreased" 154 | ``` 155 | ```jsx 156 | import { Grid, Typography } from '@material-ui/core' 157 | const [value, setValue] = React.useState(100); 158 | 159 | 160 | 161 | Maximum value 1000 162 | setValue(value)} 168 | decimalCharacter="." 169 | digitGroupSeparator="," 170 | /> 171 | 172 | 173 | Minimum value -100 174 | setValue(value)} 180 | decimalCharacter="." 181 | digitGroupSeparator="," 182 | /> 183 | 184 | 185 | ``` 186 | 187 | #### Predefined options 188 | ```html 189 | preDefined={predefinedOptions.percentageEU2dec} 190 | ``` 191 | ```jsx 192 | import { predefinedOptions } from './CurrencyTextField'; 193 | const [value, setValue] = React.useState(100); 194 | 195 | setValue(value)} 201 | /> 202 | ``` 203 | 204 | #### Usage with showing more props 205 | ```html 206 | import React from 'react' 207 | import CurrencyTextField from '@unicef/material-ui-currency-textfield' 208 | 209 | export default function MyComponent() { 210 | 211 | const [value, setValue] = React.useState(100); 212 | 213 | return ( 214 | setValue(value)} 230 | /> 231 | ); 232 | } 233 | ``` 234 | -------------------------------------------------------------------------------- /src/components/CurrencyTextField/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./CurrencyTextField" 2 | -------------------------------------------------------------------------------- /src/components/readme.md: -------------------------------------------------------------------------------- 1 | # Unicef material-ui currency textfield [![npm](https://img.shields.io/npm/v/@unicef/material-ui-currency-textfield.svg?style=flat-square)](https://www.npmjs.com/package/@unicef/material-ui-currency-textfield) 2 | 3 | [View on Github](https://github.com/unicef/material-ui-currency-textfield) 4 | ## Installation 5 | ```bash 6 | npm install @unicef/material-ui-currency-textfield --save 7 | ``` 8 | ## Usage 9 | 10 | ```html 11 | import React from 'react' 12 | import CurrencyTextField from '@unicef/material-ui-currency-textfield' 13 | 14 | export default function MyComponent() { 15 | 16 | const [value, setValue] = React.useState(); 17 | 18 | return ( 19 | setValue(value)} 26 | /> 27 | ); 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import CurrencyTextField from './components/CurrencyTextField' 2 | 3 | export default CurrencyTextField -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { styles, theme } = require('./styleguide.styles') 3 | module.exports = { 4 | title: "Material ui currency input", 5 | showUsage: true, 6 | styles, 7 | theme, 8 | showSidebar: false, 9 | webpackConfig: { 10 | module: { 11 | rules: [ 12 | // Babel loader, will use your project’s babel.config.js 13 | { 14 | test: /\.jsx?$/, 15 | exclude: /node_modules/, 16 | loader: "babel-loader", 17 | }, 18 | // Other loaders that are needed for your components 19 | { 20 | test: /\.css$/, 21 | use: ["style-loader", "css-loader"], 22 | }, 23 | ], 24 | }, 25 | }, 26 | // styleguideComponents: { 27 | // Logo: path.join(__dirname, 'src/assets/logo.png') 28 | // }, 29 | sections: [ 30 | { 31 | name: '', 32 | content: 'src/components/readme.md' 33 | }, 34 | { 35 | name: '', 36 | components: () => ([ 37 | path.resolve(__dirname, 'src/components/CurrencyTextField', 'CurrencyTextField.js'), 38 | ]) 39 | }, 40 | ], 41 | }; 42 | -------------------------------------------------------------------------------- /styleguide.styles.js: -------------------------------------------------------------------------------- 1 | const rhythm = (value = 1, unit = 'rem', basis = 1.5) => ( 2 | Array.isArray(value) 3 | ? value.map(v => `${basis * v}${unit}`).join(' ') 4 | : `${basis * value}${unit}` 5 | ) 6 | 7 | const colors = { 8 | blue: "#1CABE2", 9 | primary: '#374EA2', 10 | purple: "#6A3674", 11 | danger: "#E2231A", 12 | darkRed: "#961A49", 13 | light: '#fff', 14 | dark: '#000', 15 | grey: '#7a898f', 16 | lightGrey: '#aec0c6', 17 | paleGrey: '#ebf1f3', 18 | secondary: '#ad29b6', 19 | tertiary: '#203a44', 20 | } 21 | 22 | const theme = { 23 | color: { 24 | baseBackground: colors.light, 25 | border: colors.paleGrey, 26 | codeBackground: colors.paleGrey, 27 | error: colors.danger, 28 | light: colors.grey, 29 | lightest: colors.lightGrey, 30 | name: colors.darkBlue, 31 | type: colors.red, 32 | base: colors.dark, 33 | link: colors.primary, 34 | linkHover: colors.primary, 35 | sidebarBackground: colors.primary 36 | }, 37 | fontFamily: { 38 | base: '"proxima-nova", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif', 39 | monospace: 'Consolas, "Liberation Mono", Menlo, monospace' 40 | }, 41 | fontSize: { 42 | base: 15, 43 | text: 16, 44 | small: 13, 45 | h1: 38, 46 | h2: 32, 47 | h3: 18, 48 | h4: 18, 49 | h5: 16, 50 | h6: 16 51 | }, 52 | } 53 | 54 | const styles = { 55 | ComponentsList: { 56 | heading: { 57 | fontWeight: '700 !important' 58 | } 59 | }, 60 | Heading: { 61 | heading1: { 62 | display: 'block', 63 | position: 'relative', 64 | paddingBottom: rhythm(0.75), 65 | marginBottom: rhythm(0.75), 66 | fontWeight: 700, 67 | '&:before': { 68 | content: '""', 69 | position: 'absolute', 70 | bottom: 0, 71 | left: 0, 72 | width: rhythm(3), 73 | height: '4px', 74 | backgroundColor: colors.primary, 75 | borderRadius: '4px' 76 | }, 77 | '& > a': { 78 | fontWeight: '700 !important' 79 | } 80 | }, 81 | heading2: { 82 | marginBottom: rhythm(0.5) 83 | }, 84 | heading3: { 85 | borderBottom: `thin solid ${colors.lightGrey}`, 86 | paddingBottom: rhythm(0.25), 87 | marginBottom: rhythm(1), 88 | textTransform: 'uppercase', 89 | fontWeight: '700' 90 | } 91 | }, 92 | ReactComponent: { 93 | tabs: { 94 | backgroundColor: colors.paleGrey, 95 | padding: rhythm([0.5, 1]), 96 | overflow: 'auto' 97 | }, 98 | tabButtons: { 99 | marginBottom: 0 100 | } 101 | }, 102 | SectionHeading: { 103 | sectionName: { 104 | display: 'block', 105 | paddingTop: `${rhythm(1)} !important`, 106 | textDecoration: 'none !important', 107 | '&:hover': { 108 | opacity: 0.75 109 | } 110 | } 111 | }, 112 | StyleGuide: { 113 | content: { 114 | paddingTop: rhythm(2.5), 115 | '@media (max-width: 600px)': { 116 | padding: rhythm(1) 117 | } 118 | }, 119 | }, 120 | TabButton: { 121 | button: { 122 | width: '100%' 123 | }, 124 | isActive: { 125 | border: 0 126 | } 127 | }, 128 | } 129 | 130 | module.exports = { 131 | styles: styles, 132 | theme: theme 133 | } --------------------------------------------------------------------------------