diff --git a/.eslintrc b/.eslintrc
index 587a4b9f..bc26b3e9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -3,7 +3,11 @@
"extends": [
"airbnb",
"plugin:flowtype/recommended",
- "plugin:react/recommended"
+ "plugin:react/recommended",
+ "prettier",
+ "prettier/babel",
+ "prettier/flowtype",
+ "prettier/react"
],
"env": {
"browser": true,
@@ -31,16 +35,21 @@
},
"allowChildren": true
}],
+ "lines-between-class-members": "off",
+ "react/destructuring-assignment": "off",
"react/no-did-mount-set-state": "off",
"react/jsx-no-bind": "off",
"react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }],
"react/jsx-closing-bracket-location": 1,
+ "react/jsx-props-no-spreading": "off",
"react/prefer-stateless-function": "off",
"react/no-unused-prop-types": "off",
"react/prop-types": 0,
"react/require-default-props": "off",
"react/sort-comp": 0,
"react/display-name": ["off", { "ignoreTranspilerName": false }],
+ "react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
+ "react/static-property-placement": 0,
"class-methods-use-this": 0,
"no-duplicate-imports": 0,
"no-param-reassign": 0,
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..dd53e28c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,11 @@
+name: test
+on: [push]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: install modules
+ run: yarn
+ - name: run tests
+ run: yarn test
diff --git a/.gitignore b/.gitignore
index 4abfade2..92b003a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@ jspm_packages
# Editors
.idea
+
+# OSX
+.DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83115d1c..d1d82b32 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,47 @@ The history of all changes to react-polymorph.
vNext
=====
+### Breaking Changes :boom:
+
+- `Input` now shows errors on hover and focus by default which can be configured via two new props:
+
+```ts
+ isShowingErrorOnFocus: boolean,
+ isShowingErrorOnHover: boolean,
+```
+
+### Features :sparkles:
+
+- `Input` now accepts a new prop `themeVariables` which can be used to override css variables ([PR 173](https://github.com/input-output-hk/react-polymorph/pull/173))
+- Implemented the search functionality to the Options component ([PR 165](https://github.com/input-output-hk/daedalus/pull/165))
+- Improved PIN entry component UX ([PR 166](https://github.com/input-output-hk/react-polymorph/pull/166))
+- Enabled pasting of multiple words into Autocomplete ([PR 163](https://github.com/input-output-hk/react-polymorph/pull/163))
+
+### Fixes :muscle:
+
+- Fixed Select search issues([PR 179](https://github.com/input-output-hk/react-polymorph/pull/179))
+- Fixed Select Search styles and minor code issues ([PR 175](https://github.com/input-output-hk/react-polymorph/pull/175))
+- Fixed a wrong variable name for the select search highlight color ([PR 170](https://github.com/input-output-hk/react-polymorph/pull/170))
+- Fixed an issue related to Numeric Input when entering numbers after having selected the decimal separator ([PR 167](https://github.com/input-output-hk/react-polymorph/pull/167))
+- Fixed issues related to controlled/uncontrolled Tippy state ([PR 160](https://github.com/input-output-hk/react-polymorph/pull/160))
+- Fixed `NumericInput` to support DEBUG mode ([PR 159](https://github.com/input-output-hk/react-polymorph/pull/159))
+
+0.9.7
+=====
+
+### Features
+
+- Improve `NumericInput` component to support big numbers ([PR 152](https://github.com/input-output-hk/react-polymorph/pull/152))
+- Improve autocomplete required selections ux ([PR 154](https://github.com/input-output-hk/react-polymorph/pull/154))
+- Improve `FormField` UX by using pop overs to show errors ([PR 151](https://github.com/input-output-hk/react-polymorph/pull/151)
+
+0.9.6
+=====
+
+### Features
+
+- Added `PopOver` component (aka Smart Tooltips) ([PR 150](https://github.com/input-output-hk/react-polymorph/pull/150)
+
0.9.5
=====
diff --git a/README.md b/README.md
index e9b793ef..e932ae48 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,56 @@ const SimpleFormApp = () => (
);
```
-### Components and Skins
+## Release Managament
+
+- Starting with `1.0.0` all future releases will follow semver semantics:
+ - `patch` (eg: 1.0.x) for **API compatible bug fixes**
+ - `minor` (eg.: 1.x.0) for **API compatible new features**
+ - `major` (eg: 2.0.0) for **API breaking changes**
+
+- For early integration of upcoming release changes we use the following conventions:
+
+ - `[current version]-next.x` to tag changes for upcoming releases (as we cannot know the necessary
+ semver for the final release including all the changes). `x` in this case is simply a number that
+ is increased and can be thought of like "slots" for temporary releases
+
+ - All temporary releases should be published with the `next` npm dist tag via: `npm publish --tag next`
+ so that they are not automatically tagged with the default `latest` npm tag.
+
+- The `master` branch only includes commits of final releases
+
+- `release/x.x.x` branches are created as soon as we cut a release and know the correct semver - they
+ are always targeting the `master` branch + should be well documented. They can include many release
+ candidates which should be tagged like `[next releaes]-rc.X` where you increment X per release candidate
+ until we are confident that the release is ready to be published under its normal version.
+
+### How to publish a temporary release
+
+Temporary releases are useful for testing specific changes in your project PRs without making public
+releases that might confuse others and are not following semver.
+
+1. Create a dedicated branch for your bug/feature/chore
+2. Run `npm view react-polymorph dist-tags.next` to see the latest release version the `next` npm dist-tag is currently pointing to
+ (this will look something like this: `1.0.0-next.1`)
+3. Increase the `next.X` number by one (e.g: `npm version 1.0.0-next.2`) to create a new git tag via.
+4. Publish the release candidate with `npm publish --tag next` (to assign the `next` dist-tag instead of `latest`)
+5. Reference your release candidate version in your project PR
+
+### How to publish a stable release
+
+Stable releases are the next public version change of react-polymorph combining all previous temporary
+releases into a semver based release:
+
+1. Create a new `release/x.x.x` branch based on `develop` (following semver based on changelog)
+2. Update the version in `package.json` to the planned release version (do not tag it)
+3. Update the `CHANGELOG.md` to assign the new release version to the last changes and upcoming changes
+3. Setup a PR targetting `master` for the relase branch on Github and document the changes since last release
+4. Publish a release candidate to npm (e.g: `1.0.1-rc.1`)
+5. Integrate and test the release candidate
+6. Iterate on the release via release candidates until its ready to be merged
+7. Merge the release PR into `master` on Github and then `master` back into `develop`
+
+## Components and Skins
React-polymorph comes with simple themes & skins out of the box, but anything is customizable.
@@ -166,51 +215,39 @@ import { NumericInput } from "react-polymorph/lib/components";
import { InputSkin } from "react-polymorph/lib/skins/simple";
const MyNumericInput = () => (
-
);
```
-
-_Side Note: this shows how you can make/use specialized versions of basic components by composition
-(reusing the `InputSkin` with a specialized logic component) - a core idea of react-polymorph!_
-
-##### Expected Behavior & Limitations:
+##### Expected Behavior:
Since there is no web standard on how to build numeric input components, here is the specification we
came up with that serves our purposes in the best way:
-- Only numeric inputs that are representable by Javascript numbers are valid. This is guarded by `Number.MIN_SAFE_INTEGER`
- (-9007199254740991) and `Number.MAX_SAFE_INTEGER` (9007199254740991) but since also fractions need to
- represented, the calculation for the maximum integer part goes like this:
- `Number.MAX_SAFE_INTEGER / 10 ** (maximumFractionDigits + 1)` (which basically means that one integer digit is lost for
- each supported fraction digit). For `maximumFractionDigits == 3` this results in
- `9007199254740991 / 10 ** 4 == 900719925474.099` being the biggest number that can be entered.
-- Only numeric digits `[0-9]` and dots `.` can be entered.
+- Only numeric digits `[0-9]` and decimal separators (configurable via `bigNumberFormat` prop) can be entered.
- When invalid characters are pasted as input, nothing happens
-- When a second dot is entered it replaces the existing one and updates the fraction part accordingly
-- Commas cannot be deleted but the cursor should jump over them when DEL or BACKSPACE keys are used
-- The fraction dot can only be deleted if `minimumFractionDigits` is not defined or
- if the resulting number does not exceed the numeric limits!
-- It's possible to replace the whole number or parts of it (even the dot) by inserting another number.
-- If the fraction dot is deleted but the resulting number is too big the cursor jumps over the dot without deletion
-- If you insert a digit but the resulting number would exceed the numeric limit, nothing happens
+- When a second decimal separators is entered it replaces the existing one and updates the fraction part accordingly
+- Group separators cannot be deleted but the cursor should jump over them when DEL or BACKSPACE keys are used
+- It's possible to replace the whole number or parts of it (even the decimal separator) by inserting another number.
##### Props:
+The `NumericInput` is based on the `Input` component and extends it's functionality:
+
```js
type NumericInputProps = {
+ // Input props:
autoFocus?: boolean,
className?: string,
context: ThemeContextProp,
disabled?: boolean,
error?: string,
label?: string | Element,
- numberLocaleOptions?: Number$LocaleOptions,
onBlur?: Function,
onChange?: Function,
onFocus?: Function,
@@ -220,26 +257,36 @@ type NumericInputProps = {
theme: ?Object,
themeId: string,
themeOverrides: Object,
- useDynamicDigitCalculation: boolean,
- value: ?number,
+ // Numeric input specific props:
+ allowSigns?: boolean,
+ bigNumberFormat?: BigNumber.Format,
+ decimalPlaces?: number,
+ roundingMode?: BigNumber.RoundingMode,
+ value: ?BigNumber.Instance,
};
```
-###### `numberLocaleOptions`
+###### `value`
+
+Must be an instance of [BigNumber](https://mikemcl.github.io/bignumber.js)
+`onChange` also returns an instance of `BigNumber` after any user changes.
+
+###### `allowSigns`
+
+Is `true` by default, if `false` the user cannot enter negative numbers.
+
+###### `decimalPlaces`
+
+No restriction by default (any number of decimal places allowed).
+Can be set to fix the decimal places to a specific amount.
-`Number.toLocaleString()` is used internally to localize the given number value. This method takes options
-explained in greater detail in the
-[MDN web docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString)
+###### `bigNumberFormat`
-The most important parts are `maximumFractionDigits` (defaults to 3 as per web standard) and `minimumFractionDigits`
-which dictate the handling of fraction digits.
+You can configure the number format by passing in any valid [bignumber.js FORMAT option](https://mikemcl.github.io/bignumber.js/#format)
-###### `useDynamicDigitCalculation`
+###### `roundingMode`
-This is an optional mode that "sacrifices" simple, clear UX in favor of being able to enter bigger numbers.
-Basically it works the same way but it dynamically calculates how large the integer part of the number can
-be based on the actual fraction digits entered. The less fraction digits, the more integer digits are possible
-and vice versa.
+You can configure the rounding mode by passing in any valid [bignumber.js ROUNDING_MODE option](https://mikemcl.github.io/bignumber.js/#rounding-mode)
---
diff --git a/__tests__/FormField.test.js b/__tests__/FormField.test.js
index 16abbbff..fa24bf95 100644
--- a/__tests__/FormField.test.js
+++ b/__tests__/FormField.test.js
@@ -6,42 +6,23 @@ import { renderInSimpleTheme } from './helpers/theming';
const renderFormField = () => ;
test('FormField renders correctly', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
test('FormField renders with label', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
-});
-
-test('FormField renders with an error', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
-});
-
-test('FormField is disabled', () => {
- expect(renderInSimpleTheme(
- {disabled.toString()}}
- />
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme(
+
+ )
+ ).toMatchSnapshot();
});
test('FormField renders an input element', () => {
- expect(renderInSimpleTheme(
- }
- />
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme(
+ } />
+ )
+ ).toMatchSnapshot();
});
diff --git a/__tests__/NumericInput.behavior.test.js b/__tests__/NumericInput.behavior.test.js
index a606f973..31af0336 100644
--- a/__tests__/NumericInput.behavior.test.js
+++ b/__tests__/NumericInput.behavior.test.js
@@ -1,13 +1,15 @@
+import BigNumber from 'bignumber.js';
import React from 'react';
import { NumericInput } from '../source/components/NumericInput';
import { mountInSimpleTheme } from './helpers/theming';
describe('NumericInput onChange simulations', () => {
-
const mountNumericInputWithProps = (props) => {
const onChangeMock = jest.fn();
- const wrapper = mountInSimpleTheme();
+ const wrapper = mountInSimpleTheme(
+
+ );
const input = wrapper.find('input');
return {
input,
@@ -20,7 +22,8 @@ describe('NumericInput onChange simulations', () => {
test('valid input triggers onChange listener', () => {
const { input, onChangeMock } = mountNumericInputWithProps();
input.simulate('change', { nativeEvent: { target: { value: '19.00' } } });
- expect(onChangeMock.mock.calls[0][0]).toBe(19.00);
+ const onChangeValue = onChangeMock.mock.calls[0][0];
+ expect(onChangeValue).toEqual('19');
});
test('invalid input does not trigger onChange listener', () => {
const { input, onChangeMock } = mountNumericInputWithProps();
@@ -30,47 +33,37 @@ describe('NumericInput onChange simulations', () => {
});
describe('configurable number formats', () => {
- test('handles commas as thousand separators by default', () => {
+ test('handles commas as group separators by default', () => {
const { input, onChangeMock } = mountNumericInputWithProps();
- input.simulate('change', { nativeEvent: { target: { value: '9,999,999.00' } } });
- expect(onChangeMock.mock.calls[0][0]).toBe(9999999.00);
+ input.simulate('change', {
+ nativeEvent: { target: { value: '9,999,999.00' } },
+ });
+ const onChangeValue = onChangeMock.mock.calls[0][0];
+ expect(onChangeValue).toBe('9999999');
});
test('can be configured to handle dots as thousand separators', () => {
const { input, onChangeMock } = mountNumericInputWithProps({
- numberFormat: {
+ bigNumberFormat: {
groupSeparator: '.',
decimalSeparator: ',',
- }
+ },
+ });
+ input.simulate('change', {
+ nativeEvent: { target: { value: '9.999.999,00' } },
});
- input.simulate('change', { nativeEvent: { target: { value: '9.999.999,00' } } });
- expect(onChangeMock.mock.calls[0][0]).toBe(9999999.00);
+ expect(onChangeMock.mock.calls[0][0]).toBe('9999999');
});
test('can be configured to handle spaces as thousand separators', () => {
const { input, onChangeMock } = mountNumericInputWithProps({
- numberFormat: {
+ bigNumberFormat: {
groupSeparator: ' ',
decimalSeparator: '.',
- }
+ },
});
- input.simulate('change', { nativeEvent: { target: { value: '9 999 999.00' } } });
- expect(onChangeMock.mock.calls[0][0]).toBe(9999999.00);
- });
- });
-
- test('enforces given minimumFractionDigits', () => {
- const { input } = mountNumericInputWithProps({
- numberLocaleOptions: { minimumFractionDigits: 6 },
- value: 0,
- });
- expect(input.getDOMNode().value).toBe('0.000000');
- });
-
- test('enforces given maximumFractionDigits', () => {
- const { input } = mountNumericInputWithProps({
- numberLocaleOptions: { maximumFractionDigits: 2 },
- value: 0.123,
+ input.simulate('change', {
+ nativeEvent: { target: { value: '9 999 999.00' } },
+ });
+ expect(onChangeMock.mock.calls[0][0]).toBe('9999999');
});
- expect(input.getDOMNode().value).toBe('0.12');
});
-
});
diff --git a/__tests__/NumericInput.test.js b/__tests__/NumericInput.test.js
index ef835c0c..b05f2c06 100644
--- a/__tests__/NumericInput.test.js
+++ b/__tests__/NumericInput.test.js
@@ -1,40 +1,35 @@
+import BigNumber from 'bignumber.js';
import React from 'react';
import { NumericInput } from '../source/components/NumericInput';
import { renderInSimpleTheme } from './helpers/theming';
test('NumericInput renders correctly', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(renderInSimpleTheme()).toMatchSnapshot();
});
test('NumericInput renders with placeholder', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
test('NumericInput is disabled', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(renderInSimpleTheme()).toMatchSnapshot();
});
test('NumericInput is readOnly', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(renderInSimpleTheme()).toMatchSnapshot();
});
test('NumericInput renders with an error', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
test('NumericInput renders with a value', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
diff --git a/__tests__/TextArea.test.js b/__tests__/TextArea.test.js
index d2dee8d5..3e1760da 100644
--- a/__tests__/TextArea.test.js
+++ b/__tests__/TextArea.test.js
@@ -4,31 +4,21 @@ import { TextArea } from '../source/components/TextArea';
import { renderInSimpleTheme } from './helpers/theming';
test('TextArea renders correctly', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(renderInSimpleTheme()).toMatchSnapshot();
});
test('TextArea renders with placeholder', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
-});
-
-test('TextArea renders with an error', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
test('TextArea renders with a value', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(
+ renderInSimpleTheme()
+ ).toMatchSnapshot();
});
test('TextArea renders with 5 rows', () => {
- expect(renderInSimpleTheme(
-
- )).toMatchSnapshot();
+ expect(renderInSimpleTheme()).toMatchSnapshot();
});
diff --git a/__tests__/__snapshots__/Autocomplete.test.js.snap b/__tests__/__snapshots__/Autocomplete.test.js.snap
index deedf443..98e22ab6 100644
--- a/__tests__/__snapshots__/Autocomplete.test.js.snap
+++ b/__tests__/__snapshots__/Autocomplete.test.js.snap
@@ -9,21 +9,23 @@ exports[`Autocomplete renders correctly 1`] = `
-
+
-
+
+
+
-
+
-
+
-
+
+
+
-
+
-
- Your mnemonic phrase is incorrect
-
-
+
-
+
+
+
-
+
Enter your recovery phrase below
-
+
-
+
+
+
-
+
-
+
-
+
+
+
-
+
-
+
-
+
+
+
-
+
-
-
- true
-
-
-
-`;
-
exports[`FormField renders an input element 1`] = `