├── .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 | [](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 | 
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 [](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 | }
--------------------------------------------------------------------------------