@openedx/paragon 22.0.0-alpha.18 → 22.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/DataTable/CollapsibleButtonGroup.js +1 -1
  2. package/dist/DataTable/CollapsibleButtonGroup.js.map +1 -1
  3. package/dist/DataTable/RowStatus.js +11 -5
  4. package/dist/DataTable/RowStatus.js.map +1 -1
  5. package/dist/DataTable/TableFilters.js +1 -1
  6. package/dist/DataTable/TableFilters.js.map +1 -1
  7. package/dist/DataTable/TableFooter.js +1 -1
  8. package/dist/DataTable/TableFooter.js.map +1 -1
  9. package/dist/DataTable/filters/CheckboxFilter.js +1 -1
  10. package/dist/DataTable/filters/CheckboxFilter.js.map +1 -1
  11. package/dist/DataTable/filters/DropdownFilter.js +1 -1
  12. package/dist/DataTable/filters/DropdownFilter.js.map +1 -1
  13. package/dist/DataTable/filters/MultiSelectDropdownFilter.js +1 -1
  14. package/dist/DataTable/filters/MultiSelectDropdownFilter.js.map +1 -1
  15. package/dist/DataTable/filters/TextFilter.js +1 -1
  16. package/dist/DataTable/filters/TextFilter.js.map +1 -1
  17. package/dist/DataTable/index.js +13 -13
  18. package/dist/DataTable/index.js.map +1 -1
  19. package/dist/Dropzone/index.scss +11 -4
  20. package/dist/Form/FormControl.js +12 -6
  21. package/dist/Form/FormControl.js.map +1 -1
  22. package/dist/light.css +5 -5
  23. package/dist/light.css.map +1 -1
  24. package/dist/light.min.css +1 -1
  25. package/dist/theme-urls.json +0 -6
  26. package/package.json +6 -2
  27. package/src/ActionRow/README.md +4 -2
  28. package/src/DataTable/CollapsibleButtonGroup.jsx +1 -1
  29. package/src/DataTable/RowStatus.jsx +11 -5
  30. package/src/DataTable/TableFilters.jsx +1 -1
  31. package/src/DataTable/TableFooter.jsx +1 -5
  32. package/src/DataTable/filters/CheckboxFilter.jsx +1 -1
  33. package/src/DataTable/filters/DropdownFilter.jsx +1 -1
  34. package/src/DataTable/filters/MultiSelectDropdownFilter.jsx +1 -1
  35. package/src/DataTable/filters/TextFilter.jsx +1 -1
  36. package/src/DataTable/index.jsx +13 -13
  37. package/src/DataTable/tests/DataTable.test.jsx +1 -1
  38. package/src/DataTable/tests/RowStatus.test.jsx +2 -2
  39. package/src/DataTable/tests/SmartStatus.test.jsx +2 -2
  40. package/src/DataTable/tests/TableActions.test.jsx +5 -4
  41. package/src/Dropzone/index.scss +11 -4
  42. package/src/Form/FormControl.jsx +7 -1
  43. package/src/Form/form-control.mdx +142 -1
  44. package/src/Form/tests/FormControl.test.jsx +40 -3
  45. package/styles/css/core/variables.css +1 -5
  46. package/styles/css/themes/light/variables.css +5 -5
  47. package/styles/scss/core/core.scss +1 -0
  48. package/tokens/src/core/components/Dropzone.json +1 -5
  49. package/tokens/src/themes/light/components/Dropzone.json +9 -5
  50. package/dist/core.css +0 -16732
  51. package/dist/core.css.map +0 -1
  52. package/dist/core.min.css +0 -2
@@ -1,11 +1,5 @@
1
1
  {
2
2
  "themeUrls": {
3
- "core": {
4
- "paths": {
5
- "default": "./core.css",
6
- "minified": "./core.min.css"
7
- }
8
- },
9
3
  "defaults": {
10
4
  "light": "light"
11
5
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openedx/paragon",
3
- "version": "22.0.0-alpha.18",
3
+ "version": "22.0.0-alpha.20",
4
4
  "description": "Accessible, responsive UI component library based on Bootstrap.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -82,6 +82,7 @@
82
82
  "react-colorful": "^5.6.1",
83
83
  "react-dropzone": "^14.2.1",
84
84
  "react-focus-on": "^3.5.4",
85
+ "react-imask": "^7.1.3",
85
86
  "react-loading-skeleton": "^3.1.0",
86
87
  "react-popper": "^2.2.5",
87
88
  "react-proptype-conditional-require": "^1.0.4",
@@ -204,5 +205,8 @@
204
205
  "www",
205
206
  "icons",
206
207
  "dependent-usage-analyzer"
207
- ]
208
+ ],
209
+ "overrides": {
210
+ "@testing-library/dom": "9.3.3"
211
+ }
208
212
  }
@@ -17,6 +17,7 @@ A layout utility for the common use case of aligning buttons, links or text
17
17
  in a row in a control bar or nav.
18
18
 
19
19
  ActionRow assumes that its last child is the primary action and lays out actions so that the last item is in a primary location. If horizontal, the primary action sits on the right. If stacked, the primary action sits at the top of the stack (this is done via `flex-direction: column-reverse;`).
20
+
20
21
  ## Basic Usage
21
22
 
22
23
  ```jsx live
@@ -36,7 +37,9 @@ ActionRow can also be used with a helper component ``ActionRow.Spacer`` to inser
36
37
 
37
38
  ```jsx live
38
39
  <ActionRow>
39
- <Form.Checkbox className="flex-column flex-sm-row">Don't ask me again.</Form.Checkbox>
40
+ <Form.Checkbox className="flex-column flex-sm-row">
41
+ Don't ask me again.
42
+ </Form.Checkbox>
40
43
  <ActionRow.Spacer />
41
44
  <Button variant="tertiary">
42
45
  Cancel
@@ -49,7 +52,6 @@ ActionRow can also be used with a helper component ``ActionRow.Spacer`` to inser
49
52
 
50
53
  ## Stacked Usage
51
54
 
52
-
53
55
  ```jsx live
54
56
  <ActionRow isStacked>
55
57
  <p className="x-small">
@@ -102,7 +102,7 @@ CollapsibleButtonGroup.propTypes = {
102
102
  className: PropTypes.string,
103
103
  /** Array of action objects, containing a component and their callback args */
104
104
  actions: PropTypes.arrayOf(PropTypes.shape({
105
- component: PropTypes.oneOfType([PropTypes.func, PropTypes.element]).isRequired,
105
+ component: PropTypes.oneOfType([PropTypes.element, PropTypes.elementType]).isRequired,
106
106
  args: PropTypes.shape({}),
107
107
  })).isRequired,
108
108
  };
@@ -4,10 +4,16 @@ import { FormattedMessage } from 'react-intl';
4
4
  import DataTableContext from './DataTableContext';
5
5
 
6
6
  function RowStatus({ className, statusText }) {
7
- const { page, rows, itemCount } = useContext(DataTableContext);
8
- const pageSize = page?.length || rows?.length;
7
+ const {
8
+ page, rows, itemCount, state,
9
+ } = useContext(DataTableContext);
10
+ const rowCount = page?.length || rows?.length;
11
+ const pageSize = state?.pageSize || 0;
12
+ const pageIndex = state?.pageIndex || 0;
13
+ const firstRow = pageSize * pageIndex + 1;
14
+ const lastRow = firstRow + rowCount - 1;
9
15
 
10
- if (!pageSize) {
16
+ if (!rowCount) {
11
17
  return null;
12
18
  }
13
19
  return (
@@ -15,9 +21,9 @@ function RowStatus({ className, statusText }) {
15
21
  {statusText || (
16
22
  <FormattedMessage
17
23
  id="pgn.DataTable.RowStatus.statusText"
18
- defaultMessage="Showing {pageSize} of {itemCount}."
24
+ defaultMessage="Showing {firstRow} - {lastRow} of {itemCount}."
19
25
  description="A text describing how many rows is shown in the table"
20
- values={{ itemCount, pageSize }}
26
+ values={{ itemCount, firstRow, lastRow }}
21
27
  />
22
28
  )}
23
29
  </div>
@@ -30,7 +30,7 @@ TableFilters.defaultProps = {
30
30
 
31
31
  TableFilters.propTypes = {
32
32
  columns: PropTypes.arrayOf(PropTypes.shape({
33
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
33
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
34
34
  canFilter: PropTypes.bool,
35
35
  render: PropTypes.func.isRequired,
36
36
  })).isRequired,
@@ -25,11 +25,7 @@ function TableFooter({ className, children }) {
25
25
 
26
26
  TableFooter.propTypes = {
27
27
  /** Specifies the content of the `TableFooter` */
28
- children: PropTypes.oneOfType([
29
- PropTypes.func,
30
- PropTypes.node,
31
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.node])),
32
- ]),
28
+ children: PropTypes.node,
33
29
  /** Specifies class name to append to the base element. */
34
30
  className: PropTypes.string,
35
31
  };
@@ -62,7 +62,7 @@ CheckboxFilter.propTypes = {
62
62
  */
63
63
  column: PropTypes.shape({
64
64
  setFilter: PropTypes.func.isRequired,
65
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
65
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
66
66
  filterChoices: PropTypes.arrayOf(PropTypes.shape({
67
67
  name: PropTypes.string.isRequired,
68
68
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -50,7 +50,7 @@ DropdownFilter.propTypes = {
50
50
  */
51
51
  column: PropTypes.shape({
52
52
  setFilter: PropTypes.func.isRequired,
53
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
53
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
54
54
  filterChoices: PropTypes.arrayOf(PropTypes.shape({
55
55
  name: PropTypes.string.isRequired,
56
56
  number: PropTypes.number,
@@ -66,7 +66,7 @@ MultiSelectDropdownFilter.propTypes = {
66
66
  /** Function to set the filter value */
67
67
  setFilter: PropTypes.func.isRequired,
68
68
  /** Column header used for labels and placeholders */
69
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
69
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
70
70
  /** Names and values for the select options */
71
71
  filterChoices: PropTypes.arrayOf(PropTypes.shape({
72
72
  name: PropTypes.string.isRequired,
@@ -51,7 +51,7 @@ TextFilter.propTypes = {
51
51
  */
52
52
  column: PropTypes.shape({
53
53
  setFilter: PropTypes.func.isRequired,
54
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
54
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
55
55
  getHeaderProps: PropTypes.func.isRequired,
56
56
  filterValue: PropTypes.string,
57
57
  }).isRequired,
@@ -270,13 +270,13 @@ DataTable.propTypes = {
270
270
  /** Definition of table columns */
271
271
  columns: PropTypes.arrayOf(PropTypes.shape({
272
272
  /** User visible column name */
273
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
273
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
274
274
  /** String used to access the correct cell data for this column */
275
275
  accessor: requiredWhenNot(PropTypes.string, 'Cell'),
276
276
  /** Specifies a function that receives `row` as argument and returns cell content */
277
- Cell: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
277
+ Cell: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]),
278
278
  /** Specifies filter component */
279
- Filter: PropTypes.func,
279
+ Filter: PropTypes.elementType,
280
280
  /** Specifies filter type */
281
281
  filter: PropTypes.string,
282
282
  /** Specifies filter choices */
@@ -293,8 +293,8 @@ DataTable.propTypes = {
293
293
  /** Alternate column for selecting rows. See react table useSort docs for more information */
294
294
  manualSelectColumn: PropTypes.shape({
295
295
  id: PropTypes.string.isRequired,
296
- Header: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
297
- Cell: PropTypes.func.isRequired,
296
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]).isRequired,
297
+ Cell: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]),
298
298
  disableSortBy: PropTypes.bool.isRequired,
299
299
  }),
300
300
  /** Table columns can be sorted */
@@ -315,16 +315,16 @@ DataTable.propTypes = {
315
315
  /** defaults that will be set on each column. Will be overridden by individual column values */
316
316
  defaultColumnValues: PropTypes.shape({
317
317
  /** A default filter component for the column */
318
- Filter: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
318
+ Filter: PropTypes.elementType,
319
319
  }),
320
320
  /** Actions or other additional non-data columns can be added here */
321
321
  additionalColumns: PropTypes.arrayOf(PropTypes.shape({
322
322
  /** id must be unique from other columns ids */
323
323
  id: PropTypes.string.isRequired,
324
324
  /** column header that will be displayed to the user */
325
- Header: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
325
+ Header: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]),
326
326
  /** Component that renders in the added column. It will receive the row as a prop */
327
- Cell: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
327
+ Cell: PropTypes.oneOfType([PropTypes.elementType, PropTypes.node]),
328
328
  })),
329
329
  /** Function that will fetch table data. Called when page size, page index or filters change.
330
330
  * Meant to be used with manual filters and pagination */
@@ -401,15 +401,15 @@ DataTable.propTypes = {
401
401
  /** Number between one and four filters that can be shown on the top row. */
402
402
  numBreakoutFilters: PropTypes.oneOf([1, 2, 3, 4]),
403
403
  /** Component to be displayed when the table is empty */
404
- EmptyTableComponent: PropTypes.func,
404
+ EmptyTableComponent: PropTypes.elementType,
405
405
  /** Component to be displayed for row status, ie, 10 of 20 rows. Displayed by default in the TableControlBar */
406
- RowStatusComponent: PropTypes.func,
406
+ RowStatusComponent: PropTypes.elementType,
407
407
  /** Component to be displayed for selection status. Displayed when there are selected rows and no active filters */
408
- SelectionStatusComponent: PropTypes.func,
408
+ SelectionStatusComponent: PropTypes.elementType,
409
409
  /** Component to be displayed for filter status. Displayed when there are active filters. */
410
- FilterStatusComponent: PropTypes.func,
410
+ FilterStatusComponent: PropTypes.elementType,
411
411
  /** If children are not provided a table with control bar and footer will be rendered */
412
- children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
412
+ children: PropTypes.node,
413
413
  /** If true filters will be shown on sidebar instead */
414
414
  showFiltersInSidebar: PropTypes.bool,
415
415
  /** options for data view toggle */
@@ -135,7 +135,7 @@ describe('<DataTable />', () => {
135
135
  it('displays a control bar', () => {
136
136
  render(<DataTableWrapper {...props} />);
137
137
  expect(screen.getByTestId('table-control-bar')).toBeInTheDocument();
138
- expect(screen.getAllByText('Showing 7 of 7.')[0]).toBeInTheDocument();
138
+ expect(screen.getAllByText('Showing 1 - 7 of 7.')[0]).toBeInTheDocument();
139
139
  });
140
140
 
141
141
  it('displays a table', () => {
@@ -32,13 +32,13 @@ describe('<RowStatus />', () => {
32
32
  it('displays the row status with pagination', () => {
33
33
  const pageSize = 10;
34
34
  const { getByText } = render(<RowStatusWrapper value={{ ...instance, page: Array(pageSize) }} />);
35
- const statusText = getByText(`Showing ${pageSize} of ${instance.itemCount}.`);
35
+ const statusText = getByText(`Showing 1 - ${pageSize} of ${instance.itemCount}.`);
36
36
  expect(statusText).toBeInTheDocument();
37
37
  });
38
38
  it('displays the row status without pagination', () => {
39
39
  const pageSize = 10;
40
40
  const { getByText } = render(<RowStatusWrapper value={{ ...instance, rows: Array(pageSize) }} />);
41
- const statusText = getByText(`Showing ${pageSize} of ${instance.itemCount}.`);
41
+ const statusText = getByText(`Showing 1 - ${pageSize} of ${instance.itemCount}.`);
42
42
  expect(statusText).toBeInTheDocument();
43
43
  });
44
44
  it('sets class names on the parent', () => {
@@ -67,13 +67,13 @@ describe('<SmartStatus />', () => {
67
67
  const { getByText } = render(
68
68
  <SmartStatusWrapper value={instance} />,
69
69
  );
70
- expect(getByText(`Showing ${instance.page.length} of ${itemCount}.`)).toBeInTheDocument();
70
+ expect(getByText(`Showing 1 - ${instance.page.length} of ${itemCount}.`)).toBeInTheDocument();
71
71
  });
72
72
  it('Shows the number of items on the page if selection is off and there are no filters', () => {
73
73
  const { getByText } = render(
74
74
  <SmartStatusWrapper value={instance} />,
75
75
  );
76
- expect(getByText(`Showing ${instance.page.length} of ${itemCount}.`)).toBeInTheDocument();
76
+ expect(getByText(`Showing 1 - ${instance.page.length} of ${itemCount}.`)).toBeInTheDocument();
77
77
  });
78
78
  it('shows an alternate selection status', () => {
79
79
  const altStatusText = 'horses R cool';
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
  import classNames from 'classnames';
5
5
  import TableActions from '../TableActions';
@@ -216,9 +216,10 @@ describe('<TableActions />', () => {
216
216
  expect(overflowToggle).toBeInTheDocument();
217
217
 
218
218
  userEvent.click(overflowToggle);
219
-
220
- const buttons = screen.getAllByRole('button');
221
- expect(buttons.length).toBeGreaterThan(1);
219
+ waitFor(() => {
220
+ const buttons = screen.getAllByRole('button');
221
+ expect(buttons.length).toBeGreaterThan(1);
222
+ });
222
223
  });
223
224
 
224
225
  it('renders the correct alt text for the dropdown', () => {
@@ -8,20 +8,27 @@
8
8
  box-sizing: border-box;
9
9
  cursor: pointer;
10
10
 
11
+ &:hover,
12
+ &:focus,
13
+ &.pgn__dropzone-validation-error,
14
+ &.pgn__dropzone-active {
15
+ border-color: transparent;
16
+ }
17
+
11
18
  &:hover {
12
- border: var(--pgn-spacing-dropzone-border-hover) solid var(--pgn-color-dropzone-border-hover);
19
+ box-shadow: var(--pgn-elevation-dropzone-hover);
13
20
  }
14
21
 
15
22
  &:focus {
16
- border: var(--pgn-spacing-dropzone-border-focus) solid var(--pgn-color-dropzone-border-focus);
23
+ box-shadow: var(--pgn-elevation-dropzone-focus);
17
24
  }
18
25
 
19
26
  &.pgn__dropzone-validation-error {
20
- border: var(--pgn-spacing-dropzone-border-error) solid var(--pgn-color-dropzone-border-error);
27
+ box-shadow: var(--pgn-elevation-dropzone-error);
21
28
  }
22
29
 
23
30
  &.pgn__dropzone-active {
24
- border: var(--pgn-spacing-dropzone-border-active) solid var(--pgn-color-dropzone-border-active);
31
+ box-shadow: var(--pgn-elevation-dropzone-active);
25
32
  }
26
33
  }
27
34
 
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
4
  import RBFormControl from 'react-bootstrap/FormControl';
5
+ import { IMaskInput } from 'react-imask';
5
6
  import { useFormGroupContext } from './FormGroupContext';
6
7
  import FormControlFeedback from './FormControlFeedback';
7
8
  import FormControlDecoratorGroup from './FormControlDecoratorGroup';
@@ -17,6 +18,7 @@ const FormControl = React.forwardRef(({
17
18
  floatingLabel,
18
19
  autoResize,
19
20
  onChange,
21
+ inputMask,
20
22
  ...props
21
23
  }, ref) => {
22
24
  const {
@@ -71,7 +73,7 @@ const FormControl = React.forwardRef(({
71
73
  className={className}
72
74
  >
73
75
  <RBFormControl
74
- as={as}
76
+ as={inputMask ? IMaskInput : as}
75
77
  ref={resolvedRef}
76
78
  size={size}
77
79
  isInvalid={isInvalid}
@@ -80,6 +82,7 @@ const FormControl = React.forwardRef(({
80
82
  'has-value': hasValue,
81
83
  })}
82
84
  onChange={handleOnChange}
85
+ mask={inputMask}
83
86
  {...controlProps}
84
87
  />
85
88
  </FormControlDecoratorGroup>
@@ -122,6 +125,8 @@ FormControl.propTypes = {
122
125
  isInvalid: PropTypes.bool,
123
126
  /** Only for `as="textarea"`. Specifies whether the input can be resized according to the height of content. */
124
127
  autoResize: PropTypes.bool,
128
+ /** Specifies what format to use for the input mask. */
129
+ inputMask: PropTypes.string,
125
130
  };
126
131
 
127
132
  FormControl.defaultProps = {
@@ -140,6 +145,7 @@ FormControl.defaultProps = {
140
145
  isValid: undefined,
141
146
  isInvalid: undefined,
142
147
  autoResize: false,
148
+ inputMask: undefined,
143
149
  };
144
150
 
145
151
  export default FormControl;
@@ -43,7 +43,6 @@ or [select attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element
43
43
  }
44
44
  ```
45
45
 
46
-
47
46
  ## Input types
48
47
 
49
48
  ```jsx live
@@ -163,6 +162,148 @@ or [select attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element
163
162
  }
164
163
  ```
165
164
 
165
+ ## Input masks
166
+ Paragon uses the [react-imask](https://www.npmjs.com/package/react-imask) library,
167
+ which allows you to add masks of different types for inputs.
168
+ To create your own mask, you need to pass the required mask pattern (`+{1} (000) 000-0000`) to the `inputMask` property. <br />
169
+ See [react-imask](https://imask.js.org) for documentation on available props.
170
+
171
+ ```jsx live
172
+ () => {
173
+ {/* start example state */}
174
+ const [maskType, setMaskType] = useState('phone');
175
+ {/* end example state */}
176
+
177
+ const inputsWithMask = {
178
+ phone: (
179
+ <>
180
+ <h3>Phone</h3>
181
+ <Form.Group>
182
+ <Form.Control
183
+ inputMask="+{1} (000) 000-0000"
184
+ value={value}
185
+ onChange={handleChange}
186
+ leadingElement={<Icon src={FavoriteBorder} />}
187
+ floatingLabel="What is your phone number?"
188
+ />
189
+ </Form.Group>
190
+ </>
191
+ ),
192
+ creditCard: (<>
193
+ <h3>Credit card</h3>
194
+ <Form.Group>
195
+ <Form.Control
196
+ inputMask="0000 0000 0000 0000"
197
+ value={value}
198
+ onChange={handleChange}
199
+ leadingElement={<Icon src={FavoriteBorder} />}
200
+ floatingLabel="What is your credit card number?"
201
+ />
202
+ </Form.Group>
203
+ </>),
204
+ securePassword: (<>
205
+ <h3>Secure password</h3>
206
+ <Form.Group>
207
+ <Form.Control
208
+ inputMask="XXX-XX-0000"
209
+ value={value}
210
+ definitions={{
211
+ X: {
212
+ mask: '0',
213
+ displayChar: 'X',
214
+ placeholderChar: '#',
215
+ },
216
+ }}
217
+ onChange={handleChange}
218
+ leadingElement={<Icon src={FavoriteBorder} />}
219
+ floatingLabel="What is your password?"
220
+ />
221
+ </Form.Group>
222
+ </>),
223
+ OTPpassword: (<>
224
+ <h3>OTP password</h3>
225
+ <Form.Group>
226
+ <Form.Control
227
+ inputMask="G-00000"
228
+ value={value}
229
+ onChange={handleChange}
230
+ leadingElement={<Icon src={FavoriteBorder} />}
231
+ floatingLabel="What is your OPT password?"
232
+ />
233
+ </Form.Group>
234
+ </>),
235
+ price: (
236
+ <>
237
+ <h3>Course priсe</h3>
238
+ <Form.Group>
239
+ <Form.Control
240
+ inputMask="$num"
241
+ blocks={{
242
+ num: {
243
+ // nested masks are available!
244
+ mask: Number,
245
+ thousandsSeparator: ' '
246
+ }
247
+ }}
248
+ value={value}
249
+ onChange={handleChange}
250
+ leadingElement={<Icon src={FavoriteBorder} />}
251
+ floatingLabel="What is the price of this course?"
252
+ />
253
+ </Form.Group>
254
+ </>
255
+ ),
256
+ };
257
+
258
+ const [value, setValue] = useState('');
259
+
260
+ const handleChange = (e) => setValue(e.target.value);
261
+
262
+ return (
263
+ <>
264
+ {/* start example form block */}
265
+ <ExamplePropsForm
266
+ inputs={[
267
+ { value: maskType, setValue: setMaskType, options: Object.keys(inputsWithMask), name: 'Mask variants' },
268
+ ]}
269
+ />
270
+ {/* end example form block */}
271
+
272
+ {inputsWithMask[maskType]}
273
+ </>
274
+ );
275
+ }
276
+ ```
277
+
278
+ ## Input masks with clear value
279
+ To get a value without a mask, you need to use `onChange` instead of `onAccept` to handle changes.
280
+
281
+ ```jsx live
282
+ () => {
283
+ const [value, setValue] = useState('');
284
+
285
+ return (
286
+ <>
287
+ <Form.Group>
288
+ <Form.Control
289
+ inputMask="+{1} (000) 000-0000"
290
+ leadingElement={<Icon src={FavoriteBorder} />}
291
+ trailingElement={<Icon src={Verified} />}
292
+ floatingLabel="What is your phone number?"
293
+ value={value}
294
+ // depending on prop above first argument is
295
+ // `value` if `unmask=false`,
296
+ // `unmaskedValue` if `unmask=true`,
297
+ // `typedValue` if `unmask='typed'`
298
+ onAccept={(_, mask) => setValue(mask._unmaskedValue)}
299
+ />
300
+ </Form.Group>
301
+ Unmasked value: {JSON.stringify(value)}
302
+ </>
303
+ );
304
+ }
305
+ ```
306
+
166
307
  ## Textarea autoResize
167
308
 
168
309
  `autoResize` prop allows input to be resized according to the content height.
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { render, screen } from '@testing-library/react';
3
3
  import userEvent from '@testing-library/user-event';
4
4
 
@@ -8,8 +8,27 @@ const ref = {
8
8
  current: null,
9
9
  };
10
10
 
11
+ let unmaskedInputValue;
12
+
13
+ // eslint-disable-next-line react/prop-types
14
+ function Component({ isClearValue }) {
15
+ const [inputValue, setInputValue] = useState('');
16
+ unmaskedInputValue = inputValue;
17
+
18
+ return (
19
+ <FormControl
20
+ inputMask="+{1} (000) 000-0000"
21
+ value={inputValue}
22
+ onChange={(e) => (!isClearValue ? setInputValue(e.target.value) : null)}
23
+ /* eslint-disable-next-line no-underscore-dangle */
24
+ onAccept={(_, mask) => (isClearValue ? setInputValue(mask._unmaskedValue) : null)}
25
+ data-testid="form-control-with-mask"
26
+ />
27
+ );
28
+ }
29
+
11
30
  describe('FormControl', () => {
12
- it('textarea changes its height with autoResize prop', async () => {
31
+ it('textarea changes its height with autoResize prop', () => {
13
32
  const useReferenceSpy = jest.spyOn(React, 'useRef').mockReturnValue(ref);
14
33
  const onChangeFunc = jest.fn();
15
34
  const inputText = 'new text';
@@ -26,9 +45,27 @@ describe('FormControl', () => {
26
45
  expect(useReferenceSpy).toHaveBeenCalledTimes(1);
27
46
  expect(ref.current.style.height).toBe('0px');
28
47
 
29
- await userEvent.type(textarea, inputText);
48
+ userEvent.type(textarea, inputText);
30
49
 
31
50
  expect(onChangeFunc).toHaveBeenCalledTimes(inputText.length);
32
51
  expect(ref.current.style.height).toEqual(`${ref.current.scrollHeight + ref.current.offsetHeight}px`);
33
52
  });
53
+
54
+ it('should apply and accept input mask for phone numbers', () => {
55
+ render(<Component />);
56
+
57
+ const input = screen.getByTestId('form-control-with-mask');
58
+ userEvent.type(input, '5555555555');
59
+ expect(input.value).toBe('+1 (555) 555-5555');
60
+ });
61
+
62
+ it('should be cleared from the mask elements value', () => {
63
+ render(<Component isClearValue />);
64
+
65
+ const input = screen.getByTestId('form-control-with-mask');
66
+ userEvent.type(input, '5555555555');
67
+
68
+ expect(input.value).toBe('+1 (555) 555-5555');
69
+ expect(unmaskedInputValue).toBe('15555555555');
70
+ });
34
71
  });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * IMPORTANT: This file is the result of assembling design tokens
3
3
  * Do not edit directly
4
- * Generated on Thu, 16 Nov 2023 10:44:11 GMT
4
+ * Generated on Wed, 06 Dec 2023 09:28:09 GMT
5
5
  */
6
6
 
7
7
  :root {
@@ -195,10 +195,6 @@
195
195
  --pgn-spacing-form-input-check-margin-x-inline: .3125rem;
196
196
  --pgn-spacing-form-input-check-margin-x-base: .25rem;
197
197
  --pgn-spacing-form-input-check-gutter: 1.25rem;
198
- --pgn-spacing-dropzone-border-error: 2px;
199
- --pgn-spacing-dropzone-border-active: 2px;
200
- --pgn-spacing-dropzone-border-focus: 2px;
201
- --pgn-spacing-dropzone-border-hover: 2px;
202
198
  --pgn-spacing-dropzone-border-base: 1px;
203
199
  --pgn-spacing-dropzone-padding: 1.5rem;
204
200
  --pgn-spacing-dropdown-close-container-top: .625rem;