From c9c79cfe2ea951d438297c820b4fce5bd36f535a Mon Sep 17 00:00:00 2001 From: Sawyer Hollenshead Date: Mon, 12 Mar 2018 10:25:22 -0400 Subject: [PATCH] Export unmaskValue method (#255) * Add unmask and unmaskValue export * All commented import statements in code examples * Document unmaskValue method * Update yarn.lock after peerDependencies update in #253 --- .../TextField/CurrencyField.example.jsx | 6 +- .../components/TextField/CurrencyField.scss | 4 + .../core/src/components/TextField/Mask.jsx | 17 +++ .../src/components/TextField/Mask.test.jsx | 28 ++++- .../src/components/TextField/TextField.jsx | 4 +- .../components/TextField/TextField.test.jsx | 8 +- .../__snapshots__/TextField.test.jsx.snap | 2 +- packages/core/yarn.lock | 106 ------------------ tools/gulp/docs/parseReactFile.js | 2 +- 9 files changed, 64 insertions(+), 113 deletions(-) diff --git a/packages/core/src/components/TextField/CurrencyField.example.jsx b/packages/core/src/components/TextField/CurrencyField.example.jsx index 2cd5657da8..d10c56fefc 100644 --- a/packages/core/src/components/TextField/CurrencyField.example.jsx +++ b/packages/core/src/components/TextField/CurrencyField.example.jsx @@ -1,6 +1,6 @@ +import TextField, { unmaskValue } from './TextField'; import React from 'react'; import ReactDOM from 'react-dom'; -import TextField from './TextField'; ReactDOM.render( { + // import { unmaskValue } from '@cmsgov/design-system-core'; + console.log('Unmasked value:', unmaskValue(e.target.value, 'currency')); + }} value="2500" />, document.getElementById('js-example') diff --git a/packages/core/src/components/TextField/CurrencyField.scss b/packages/core/src/components/TextField/CurrencyField.scss index 456977f87c..407a719080 100644 --- a/packages/core/src/components/TextField/CurrencyField.scss +++ b/packages/core/src/components/TextField/CurrencyField.scss @@ -21,6 +21,10 @@ Style guide: components.currency-field /* `` +Passing a `mask="currency"` prop into the `TextField` component will +enable formatting to occur when the field is blurred. To "unmask" the +value, you can import the `unmaskValue` method. + @react-component TextField @react-example CurrencyField diff --git a/packages/core/src/components/TextField/Mask.jsx b/packages/core/src/components/TextField/Mask.jsx index 602d13bd12..52ea231d18 100644 --- a/packages/core/src/components/TextField/Mask.jsx +++ b/packages/core/src/components/TextField/Mask.jsx @@ -146,4 +146,21 @@ Mask.propTypes = { mask: PropTypes.string.isRequired }; +/** + * Remove mask characters from value + * @param {String} value + * @param {String} mask + * @returns {String} + */ +export function unmask(value, mask) { + if (!value) return value; + + if (mask === 'currency') { + // Preserve only digits, decimal point, or negative symbol + value = value.match(/^-|[\d.]/g).join(''); + } + + return value; +} + export default Mask; diff --git a/packages/core/src/components/TextField/Mask.test.jsx b/packages/core/src/components/TextField/Mask.test.jsx index 460993ba04..c1cd5301c7 100644 --- a/packages/core/src/components/TextField/Mask.test.jsx +++ b/packages/core/src/components/TextField/Mask.test.jsx @@ -1,5 +1,5 @@ +import Mask, { unmask } from './Mask'; import { mount, shallow } from 'enzyme'; -import Mask from './Mask'; import React from 'react'; function render(customProps = {}, inputProps = {}, deep = false) { @@ -123,3 +123,29 @@ describe('Mask', function() { }); }); }); + +describe('unmask', () => { + it('returns value when mask is undefined', () => { + expect(unmask('1,234')).toBe('1,234'); + }); + + it('returns value when mask is unknown', () => { + expect(unmask('1,234', 'foo')).toBe('1,234'); + }); + + it('exits when value is undefined or null', () => { + expect(unmask()).toBeUndefined(); + expect(unmask(null)).toBeNull(); + }); + + it('removes mask from currency value', () => { + const name = 'currency'; + + expect(unmask('', name)).toBe(''); + expect(unmask(' 1,234 ', name)).toBe('1234'); // whitespace + expect(unmask('1,234', name)).toBe('1234'); + expect(unmask('1,234.5', name)).toBe('1234.5'); + expect(unmask('1,234,000.50', name)).toBe('1234000.50'); + expect(unmask('-1,234,000.50', name)).toBe('-1234000.50'); + }); +}); diff --git a/packages/core/src/components/TextField/TextField.jsx b/packages/core/src/components/TextField/TextField.jsx index 11d8a27367..848e5f692e 100644 --- a/packages/core/src/components/TextField/TextField.jsx +++ b/packages/core/src/components/TextField/TextField.jsx @@ -1,10 +1,12 @@ +import Mask, { unmask } from './Mask'; import FormLabel from '../FormLabel/FormLabel'; -import Mask from './Mask'; import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import uniqueId from 'lodash.uniqueid'; +export { unmask as unmaskValue }; + /** * A `TextField` component renders an input field as well as supporting UI * elements like a label, error message, and hint text. diff --git a/packages/core/src/components/TextField/TextField.test.jsx b/packages/core/src/components/TextField/TextField.test.jsx index 0e033bcd8a..4968b1daaf 100644 --- a/packages/core/src/components/TextField/TextField.test.jsx +++ b/packages/core/src/components/TextField/TextField.test.jsx @@ -1,6 +1,6 @@ +import TextField, { unmaskValue } from './TextField'; import { mount, shallow } from 'enzyme'; import React from 'react'; -import TextField from './TextField'; function render(customProps = {}, deep = false) { const props = Object.assign( @@ -274,7 +274,11 @@ describe('TextField', function() { }); }); - describe('masked', () => { + describe('masks', () => { + it('exports unmaskValue method', () => { + expect(typeof unmaskValue).toBe('function'); + }); + it('renders currency mask', () => { const data = render({ mask: 'currency' diff --git a/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap b/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap index b78c8291e8..2a001abb07 100644 --- a/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap +++ b/packages/core/src/components/TextField/__snapshots__/TextField.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TextField masked renders currency mask 1`] = ` +exports[`TextField masks renders currency mask 1`] = `
diff --git a/packages/core/yarn.lock b/packages/core/yarn.lock index 89429ea793..0e622f5730 100644 --- a/packages/core/yarn.lock +++ b/packages/core/yarn.lock @@ -2,18 +2,10 @@ # yarn lockfile v1 -asap@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" - classnames@^2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - core-js@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" @@ -22,28 +14,10 @@ downshift@^1.28.2: version "1.28.2" resolved "https://registry.yarnpkg.com/downshift/-/downshift-1.28.2.tgz#ff5b4e89ff439943a8e58890993015199604e1e6" -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - dependencies: - iconv-lite "~0.4.13" - ev-emitter@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ev-emitter/-/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" -fbjs@^0.8.16: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.9" - focus-trap-react@^3.0.4: version "3.0.5" resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.0.5.tgz#8fb381b92eafe075c2406297d1da618650d37143" @@ -56,64 +30,14 @@ focus-trap@^2.0.1: dependencies: tabbable "^1.0.3" -iconv-lite@~0.4.13: - version "0.4.15" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -js-tokens@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" - lodash.uniqueid@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.uniqueid/-/lodash.uniqueid-4.0.1.tgz#3268f26a7c88e4f4b1758d679271814e31fa5b26" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - no-scroll@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/no-scroll/-/no-scroll-2.1.0.tgz#f8643b3ddb6a3bf94430e5ff31d26f21d082a695" -node-fetch@^1.0.1: - version "1.6.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -promise@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" - dependencies: - asap "~2.0.3" - -"prop-types@^15.0.0 || ^16.0.0", prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - react-aria-modal@^2.11.1: version "2.11.1" resolved "https://registry.yarnpkg.com/react-aria-modal/-/react-aria-modal-2.11.1.tgz#b82769f369223f634989728694c9eecacb0a8529" @@ -126,36 +50,6 @@ react-displace@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-displace/-/react-displace-2.3.0.tgz#6915f8f2f279a29a7b58442405c26edc3d441429" -"react-dom@^15.0.0 || ^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0.tgz#9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -"react@^15.0.0 || ^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - tabbable@^1.0.3: version "1.1.1" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.1.tgz#88618caa0dc76e877572a678f0ad97519bba6761" - -ua-parser-js@^0.7.9: - version "0.7.12" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" - -whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/tools/gulp/docs/parseReactFile.js b/tools/gulp/docs/parseReactFile.js index 3b268e019f..c5e83b0088 100644 --- a/tools/gulp/docs/parseReactFile.js +++ b/tools/gulp/docs/parseReactFile.js @@ -45,7 +45,7 @@ function parseExample(file) { let source = file.contents.toString(); // We use relative paths for the components, so this removes the import statements // from the example code to avoid causing confusion - const imports = source.match(/import.+/g); + const imports = source.match(/^import.+/gm); if (imports) { // Remove everything up to the end of the last import statement const lastImport = imports[imports.length - 1];