@transferwise/components 46.6.0 → 46.8.0

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 (148) hide show
  1. package/build/index.esm.js +290 -346
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +290 -345
  4. package/build/index.js.map +1 -1
  5. package/build/main.css +107 -17
  6. package/build/styles/inputs/Input.css +0 -4
  7. package/build/styles/inputs/SelectInput.css +6 -1
  8. package/build/styles/inputs/TextArea.css +0 -4
  9. package/build/styles/main.css +107 -17
  10. package/build/styles/segmentedControl/SegmentedControl.css +101 -0
  11. package/build/styles/select/Select.css +0 -4
  12. package/build/types/common/locale/index.d.ts +26 -43
  13. package/build/types/common/locale/index.d.ts.map +1 -1
  14. package/build/types/index.d.ts +3 -0
  15. package/build/types/index.d.ts.map +1 -1
  16. package/build/types/inputs/SelectInput.d.ts +6 -5
  17. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  18. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +22 -27
  19. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  20. package/build/types/phoneNumberInput/data/countries.d.ts +5 -10
  21. package/build/types/phoneNumberInput/data/countries.d.ts.map +1 -1
  22. package/build/types/phoneNumberInput/index.d.ts +1 -1
  23. package/build/types/phoneNumberInput/index.d.ts.map +1 -1
  24. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts +1 -1
  25. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts.map +1 -1
  26. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts +1 -1
  27. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts.map +1 -1
  28. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +8 -1
  29. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
  30. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts +1 -1
  31. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts.map +1 -1
  32. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts +8 -4
  33. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts.map +1 -1
  34. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts +1 -1
  35. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts.map +1 -1
  36. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts +1 -1
  37. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts.map +1 -1
  38. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +2 -1
  39. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
  40. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts +1 -1
  41. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts.map +1 -1
  42. package/build/types/phoneNumberInput/utils/index.d.ts +11 -13
  43. package/build/types/phoneNumberInput/utils/index.d.ts.map +1 -1
  44. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts +1 -1
  45. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts.map +1 -1
  46. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts +1 -1
  47. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts.map +1 -1
  48. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts +1 -1
  49. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts.map +1 -1
  50. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts +6 -1
  51. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts.map +1 -1
  52. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +2 -1
  53. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
  54. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts +7 -1
  55. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts.map +1 -1
  56. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts +1 -1
  57. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts.map +1 -1
  58. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
  59. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
  60. package/build/types/segmentedControl/SegmentedControl.d.ts +31 -0
  61. package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -0
  62. package/build/types/segmentedControl/index.d.ts +3 -0
  63. package/build/types/segmentedControl/index.d.ts.map +1 -0
  64. package/package.json +7 -17
  65. package/src/common/locale/{index.spec.js → index.spec.ts} +4 -4
  66. package/src/common/locale/index.ts +96 -0
  67. package/src/index.ts +3 -0
  68. package/src/inputs/Input.css +0 -4
  69. package/src/inputs/SelectInput.css +6 -1
  70. package/src/inputs/SelectInput.less +8 -1
  71. package/src/inputs/SelectInput.spec.tsx +26 -0
  72. package/src/inputs/SelectInput.story.tsx +73 -1
  73. package/src/inputs/SelectInput.tsx +104 -85
  74. package/src/inputs/TextArea.css +0 -4
  75. package/src/main.css +107 -17
  76. package/src/main.less +1 -0
  77. package/src/phoneNumberInput/PhoneNumberInput.spec.js +18 -22
  78. package/src/phoneNumberInput/PhoneNumberInput.tsx +193 -0
  79. package/src/phoneNumberInput/data/{countries.js → countries.ts} +9 -1
  80. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +3 -0
  81. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.spec.js → excludeCountries.spec.ts} +1 -1
  82. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.js → excludeCountries.ts} +6 -5
  83. package/src/phoneNumberInput/utils/explodeNumberModel/{explodeNumberModel.spec.js → explodeNumberModel.spec.ts} +1 -1
  84. package/src/phoneNumberInput/utils/explodeNumberModel/index.ts +24 -0
  85. package/src/phoneNumberInput/utils/findCountryByCode/{findCountryByCode.spec.js → findCountryByCode.spec.ts} +0 -1
  86. package/src/phoneNumberInput/utils/findCountryByCode/index.ts +12 -0
  87. package/src/phoneNumberInput/utils/findCountryByPrefix/index.ts +12 -0
  88. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.ts +102 -0
  89. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +12 -0
  90. package/src/phoneNumberInput/utils/{index.js → index.ts} +0 -2
  91. package/src/phoneNumberInput/utils/isStringNumeric/{isStringNumeric.spec.js → isStringNumeric.spec.ts} +0 -1
  92. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.ts +1 -0
  93. package/src/phoneNumberInput/utils/isValidPhoneNumber/{isValidPhoneNumber.spec.js → isValidPhoneNumber.spec.ts} +1 -1
  94. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.ts +7 -0
  95. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +4 -0
  96. package/src/phoneNumberInput/utils/setDefaultPrefix/index.ts +20 -0
  97. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +6 -0
  98. package/src/segmentedControl/SegmentedControl.css +101 -0
  99. package/src/segmentedControl/SegmentedControl.less +101 -0
  100. package/src/segmentedControl/SegmentedControl.spec.tsx +106 -0
  101. package/src/segmentedControl/SegmentedControl.story.tsx +55 -0
  102. package/src/segmentedControl/SegmentedControl.tsx +175 -0
  103. package/src/segmentedControl/index.ts +2 -0
  104. package/src/select/Select.css +0 -4
  105. package/src/ssr.spec.js +17 -0
  106. package/src/withDisplayFormat/WithDisplayFormat.spec.js +1 -1
  107. package/src/withDisplayFormat/WithDisplayFormat.tsx +1 -1
  108. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts +0 -2
  109. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts.map +0 -1
  110. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts +0 -2
  111. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts.map +0 -1
  112. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts +0 -3
  113. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts.map +0 -1
  114. package/build/types/utilities/wrapInFragment.d.ts +0 -3
  115. package/build/types/utilities/wrapInFragment.d.ts.map +0 -1
  116. package/src/common/locale/index.js +0 -139
  117. package/src/phoneNumberInput/PhoneNumberInput.js +0 -210
  118. package/src/phoneNumberInput/data/countries.spec.js +0 -12
  119. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.js +0 -4
  120. package/src/phoneNumberInput/utils/explodeNumberModel/index.js +0 -27
  121. package/src/phoneNumberInput/utils/filterOptionsForQuery/filterOptionsForQuery.spec.js +0 -36
  122. package/src/phoneNumberInput/utils/filterOptionsForQuery/index.js +0 -11
  123. package/src/phoneNumberInput/utils/findCountryByCode/index.js +0 -10
  124. package/src/phoneNumberInput/utils/findCountryByPrefix/index.js +0 -11
  125. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.js +0 -26
  126. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.js +0 -67
  127. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js +0 -1
  128. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js +0 -25
  129. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.spec.js +0 -66
  130. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.js +0 -1
  131. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.js +0 -10
  132. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.js +0 -2
  133. package/src/phoneNumberInput/utils/setDefaultPrefix/index.js +0 -25
  134. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.js +0 -3
  135. package/src/utilities/wrapInFragment.tsx +0 -3
  136. /package/src/phoneNumberInput/{PhoneNumberInput.story.js → PhoneNumberInput.story.tsx} +0 -0
  137. /package/src/phoneNumberInput/{index.js → index.ts} +0 -0
  138. /package/src/phoneNumberInput/utils/cleanNumber/{cleanNumber.spec.js → cleanNumber.spec.ts} +0 -0
  139. /package/src/phoneNumberInput/utils/cleanNumber/{index.js → index.ts} +0 -0
  140. /package/src/phoneNumberInput/utils/excludeCountries/{index.js → index.ts} +0 -0
  141. /package/src/phoneNumberInput/utils/findCountryByPrefix/{findCountryByPrefix.spec.js → findCountryByPrefix.spec.ts} +0 -0
  142. /package/src/phoneNumberInput/utils/groupCountriesByPrefix/{index.js → index.ts} +0 -0
  143. /package/src/phoneNumberInput/utils/isStringNumeric/{index.js → index.ts} +0 -0
  144. /package/src/phoneNumberInput/utils/isValidPhoneNumber/{index.js → index.ts} +0 -0
  145. /package/src/phoneNumberInput/utils/longestMatchingPrefix/{longestMatchingPrefix.spec.js → longestMatchingPrefix.spec.ts} +0 -0
  146. /package/src/phoneNumberInput/utils/setDefaultPrefix/{setDefaultPrefix.spec.js → setDefaultPrefix.spec.ts} +0 -0
  147. /package/src/phoneNumberInput/utils/sortArrayByProperty/{index.js → index.ts} +0 -0
  148. /package/src/phoneNumberInput/utils/sortArrayByProperty/{sortArrayByProperty.spec.js → sortArrayByProperty.spec.ts} +0 -0
@@ -0,0 +1,101 @@
1
+ .segmented-control {
2
+ box-sizing: border-box;
3
+ --segment-highlight-width: 0;
4
+ --segment-highlight-x: var(--size-4);
5
+ }
6
+ .segmented-control__segments {
7
+ display: inline-flex;
8
+ position: relative;
9
+ padding: 4px;
10
+ padding: var(--size-4);
11
+ width: 100%;
12
+ justify-content: center;
13
+ align-items: center;
14
+ background: rgba(134,167,189,0.10196);
15
+ background: var(--color-background-neutral);
16
+ border-radius: 24px;
17
+ border-radius: var(--size-24);
18
+ transition: outline 300ms;
19
+ outline: 2px solid transparent;
20
+ }
21
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
22
+ outline: 2px solid var(--color-interactive-primary);
23
+ }
24
+ .segmented-control__segments::after {
25
+ content: "";
26
+ position: absolute;
27
+ width: var(--segment-highlight-width);
28
+ top: 4px;
29
+ top: var(--size-4);
30
+ bottom: 4px;
31
+ bottom: var(--size-4);
32
+ left: var(--segment-highlight-x);
33
+ z-index: 0;
34
+ background: #ffffff;
35
+ background: var(--color-background-screen);
36
+ border-radius: 24px;
37
+ border-radius: var(--size-24);
38
+ transition: left 300ms;
39
+ }
40
+ .segmented-control__segments--no-animate::after {
41
+ transition: none !important;
42
+ }
43
+ .segmented-control__segment {
44
+ position: relative;
45
+ flex: 1 1 100%;
46
+ flex-flow: column;
47
+ padding: 8px 16px;
48
+ padding: var(--size-8) var(--size-16);
49
+ margin: 0 0 0 4px;
50
+ margin: 0 0 0 var(--size-4);
51
+ min-width: 0;
52
+ line-height: inherit;
53
+ align-items: center;
54
+ text-align: center;
55
+ vertical-align: middle;
56
+ border-radius: 24px;
57
+ border-radius: var(--size-24);
58
+ z-index: 1;
59
+ cursor: pointer;
60
+ transition: background 300ms;
61
+ color: var(--color-interactive-primary);
62
+ }
63
+ .segmented-control__segment:first-child {
64
+ margin-left: 0;
65
+ }
66
+ .segmented-control__segment:hover {
67
+ background: rgba(0,0,0,0.10196);
68
+ background: var(--color-background-overlay);
69
+ }
70
+ .segmented-control__radio-input {
71
+ position: fixed;
72
+ opacity: 0;
73
+ pointer-events: none;
74
+ }
75
+ .segmented-control__button {
76
+ width: 100%;
77
+ height: 100%;
78
+ background: none;
79
+ -webkit-appearance: none;
80
+ -moz-appearance: none;
81
+ appearance: none;
82
+ border: none;
83
+ outline: none;
84
+ font: inherit;
85
+ outline: 2px solid transparent;
86
+ }
87
+ .segmented-control__button:focus {
88
+ outline-offset: 0px;
89
+ }
90
+ .segmented-control__button:focus-visible {
91
+ outline-color: var(--color-interactive-primary);
92
+ }
93
+ .segmented-control__selected-segment:hover {
94
+ background: transparent;
95
+ }
96
+ .segmented-control__text {
97
+ word-wrap: break-word;
98
+ word-break: break-word;
99
+ color: var(--color-interactive-primary);
100
+ transition: font-weight 300ms;
101
+ }
@@ -0,0 +1,101 @@
1
+ .segmented-control {
2
+ box-sizing: border-box;
3
+ --segment-highlight-width: 0;
4
+ --segment-highlight-x: var(--size-4);
5
+ }
6
+
7
+ .segmented-control__segments {
8
+ display: inline-flex;
9
+ position: relative;
10
+ padding: var(--size-4);
11
+ width: 100%;
12
+ justify-content: center;
13
+ align-items: center;
14
+ background: var(--color-background-neutral);
15
+ border-radius: var(--size-24);
16
+ transition: outline 300ms;
17
+ outline: 2px solid transparent;
18
+ }
19
+
20
+ .segmented-control--input:has(:focus-visible) > .segmented-control__segments::after {
21
+ outline: 2px solid var(--color-interactive-primary);
22
+ }
23
+
24
+ .segmented-control__segments::after {
25
+ content: "";
26
+ position: absolute;
27
+ width: var(--segment-highlight-width);
28
+ top: var(--size-4);
29
+ bottom: var(--size-4);
30
+ left: var(--segment-highlight-x);
31
+ z-index: 0;
32
+ background: var(--color-background-screen);
33
+ border-radius: var(--size-24);
34
+ transition: left 300ms;
35
+ }
36
+
37
+ .segmented-control__segments--no-animate::after {
38
+ transition: none !important;
39
+ }
40
+
41
+ .segmented-control__segment {
42
+ position: relative;
43
+ flex: 1 1 100%;
44
+ flex-flow: column;
45
+ padding: var(--size-8) var(--size-16);
46
+ margin: 0 0 0 var(--size-4);
47
+ min-width: 0;
48
+ line-height: inherit;
49
+ align-items: center;
50
+ text-align: center;
51
+ vertical-align: middle;
52
+ border-radius: var(--size-24);
53
+ z-index: 1;
54
+ cursor: pointer;
55
+ transition: background 300ms;
56
+ color: var(--color-interactive-primary);
57
+ }
58
+
59
+ .segmented-control__segment:first-child {
60
+ margin-left: 0;
61
+ }
62
+
63
+ .segmented-control__segment:hover {
64
+ background: var(--color-background-overlay);
65
+ }
66
+
67
+ .segmented-control__radio-input {
68
+ position: fixed;
69
+ opacity: 0;
70
+ pointer-events: none;
71
+ }
72
+
73
+ .segmented-control__button {
74
+ width: 100%;
75
+ height: 100%;
76
+ background: none;
77
+ appearance: none;
78
+ border: none;
79
+ outline: none;
80
+ font: inherit;
81
+ outline: 2px solid transparent;
82
+ }
83
+
84
+ .segmented-control__button:focus {
85
+ outline-offset: 0px;
86
+ }
87
+
88
+ .segmented-control__button:focus-visible {
89
+ outline-color: var(--color-interactive-primary);
90
+ }
91
+
92
+ .segmented-control__selected-segment:hover {
93
+ background: transparent;
94
+ }
95
+
96
+ .segmented-control__text {
97
+ word-wrap: break-word;
98
+ word-break: break-word;
99
+ color: var(--color-interactive-primary);
100
+ transition: font-weight 300ms;
101
+ }
@@ -0,0 +1,106 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen, userEvent } from '../test-utils';
3
+
4
+ import SegmentedControl, { SegmentedControlProps } from './SegmentedControl';
5
+
6
+ const defaultSegments = [
7
+ {
8
+ id: '1',
9
+ value: 'accounting',
10
+ label: 'Accounting',
11
+ },
12
+ {
13
+ id: '2',
14
+ value: 'payroll',
15
+ label: 'Payroll',
16
+ },
17
+ {
18
+ id: '3',
19
+ value: 'reporting',
20
+ label: 'Reporting',
21
+ },
22
+ ];
23
+
24
+ const defaultSegmentsWithControls = defaultSegments.map((segment, index) => ({
25
+ ...segment,
26
+ controls: `aControlId${index}`,
27
+ }));
28
+
29
+ const onChange = jest.fn();
30
+
31
+ const defaultProps: SegmentedControlProps = {
32
+ name: 'segmentedControl',
33
+ defaultValue: defaultSegments[0].value,
34
+ mode: 'input',
35
+ segments: defaultSegments,
36
+ onChange,
37
+ };
38
+
39
+ const renderSegmentedControl = (overrides: Partial<SegmentedControlProps> = {}) => {
40
+ return render(<SegmentedControl {...defaultProps} {...(overrides as SegmentedControlProps)} />);
41
+ };
42
+
43
+ describe('SegmentedControl', () => {
44
+ it('dynamically adds inline style props for animated segment overlay', () => {
45
+ renderSegmentedControl();
46
+
47
+ const segmentedControls = screen.getByTestId('segmented-control');
48
+
49
+ expect(segmentedControls).toHaveStyle(
50
+ '--segment-highlight-width: 0px; --segment-highlight-x: 0px;',
51
+ );
52
+ });
53
+
54
+ it('default value is selected', () => {
55
+ renderSegmentedControl({ mode: 'view', segments: defaultSegmentsWithControls });
56
+
57
+ const accountingTab = screen.getByRole('tab', { name: 'Accounting' });
58
+ expect(accountingTab).toHaveAttribute('aria-selected', 'true');
59
+ });
60
+
61
+ it('lets the user pick through the segments when it is set to input', () => {
62
+ renderSegmentedControl();
63
+
64
+ const payroll = screen.getByRole('radio', { name: 'Payroll' });
65
+ userEvent.click(payroll);
66
+
67
+ expect(onChange).toHaveBeenCalledWith('payroll');
68
+
69
+ const reporting = screen.getByRole('radio', { name: 'Reporting' });
70
+ userEvent.click(reporting);
71
+
72
+ expect(onChange).toHaveBeenCalledWith('reporting');
73
+ });
74
+
75
+ it('lets the user pick through the segments when it is set to view', () => {
76
+ renderSegmentedControl({ mode: 'view', segments: defaultSegmentsWithControls });
77
+
78
+ const payroll = screen.getByRole('tab', { name: 'Payroll' });
79
+ userEvent.click(payroll);
80
+
81
+ expect(onChange).toHaveBeenCalledWith('payroll');
82
+
83
+ const reporting = screen.getByRole('tab', { name: 'Reporting' });
84
+ userEvent.click(reporting);
85
+
86
+ expect(onChange).toHaveBeenCalledWith('reporting');
87
+ });
88
+
89
+ it('throws error if user tries to add too many segments', () => {
90
+ expect(() => {
91
+ renderSegmentedControl({
92
+ mode: 'input',
93
+ segments: [
94
+ ...defaultSegments,
95
+ {
96
+ id: '4',
97
+ value: 'anotherOne',
98
+ label: 'Another One',
99
+ },
100
+ ],
101
+ });
102
+ }).toThrow(
103
+ 'SegmentedControl only supports up to 3 segments. Please refer to: https://wise.design/components/segmented-control',
104
+ );
105
+ });
106
+ });
@@ -0,0 +1,55 @@
1
+ import { StoryFn } from '@storybook/react';
2
+ import React from 'react';
3
+
4
+ import SegmentedControl, { Segments } from './SegmentedControl';
5
+
6
+ export default {
7
+ component: SegmentedControl,
8
+ title: 'Forms/SegmentedControl',
9
+ };
10
+
11
+ const segments: Segments = [
12
+ { id: 'CUPCAKE', label: 'Cupcakes', value: 'cupcakes' },
13
+ { id: 'SPONGECAKE', label: 'Sponge cake', value: 'spongecake' },
14
+ { id: 'CARROT_CAKE', label: 'Carrot cake', value: 'carrotcake' },
15
+ ];
16
+
17
+ const segmentsWithControls: Segments = [
18
+ { id: 'CUPCAKE', label: 'Cupcakes', value: 'cupcakes', controls: 'aControlId' },
19
+ { id: 'SPONGECAKE', label: 'Sponge cake', value: 'spongecake', controls: 'aControlId' },
20
+ { id: 'CARROT_CAKE', label: 'Carrot cake', value: 'carrotcake', controls: 'aControlId' },
21
+ ];
22
+
23
+ const Template: StoryFn = (args) => {
24
+ const [selectedValue, setSelectedValue] = React.useState(segments[0].value);
25
+
26
+ return (
27
+ <div className="p-a-2">
28
+ <SegmentedControl
29
+ name="aSegmentedControl"
30
+ defaultValue={selectedValue}
31
+ onChange={setSelectedValue}
32
+ {...(args.mode === 'view'
33
+ ? { segments: segmentsWithControls, mode: 'view', controls: 'aControlId' }
34
+ : { segments, mode: 'input' })}
35
+ />
36
+ <div className="m-a-2" id="aControlId">
37
+ <p>Selected value: {selectedValue}</p>
38
+ </div>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export const SegmentedControlDefault = {
44
+ render: Template,
45
+ args: {
46
+ mode: 'input',
47
+ },
48
+ };
49
+
50
+ export const SegmentedControlView = {
51
+ render: Template,
52
+ args: {
53
+ mode: 'view',
54
+ },
55
+ };
@@ -0,0 +1,175 @@
1
+ import classNames from 'classnames';
2
+ import { createRef, useEffect, useRef, useState } from 'react';
3
+
4
+ import Body from '../body';
5
+ import { Typography } from '../common';
6
+
7
+ type SegmentBase = { id: string; label: string; value: string };
8
+
9
+ type Segment = SegmentBase & { controls?: never };
10
+ type SegmentWithControls = SegmentBase & { controls: string };
11
+
12
+ export type Segments = Segment[] | SegmentWithControls[];
13
+
14
+ type SegmentedControlPropsBase = {
15
+ name: string;
16
+ defaultValue: string;
17
+ mode: 'input' | 'view';
18
+ onChange: (value: string) => void;
19
+ };
20
+
21
+ type SegmentedControlViewProps = {
22
+ mode: 'view';
23
+ segments: SegmentWithControls[];
24
+ };
25
+
26
+ type SegmentedControlInputProps = {
27
+ mode: 'input';
28
+ segments: Segment[];
29
+ };
30
+
31
+ export type SegmentedControlProps = SegmentedControlPropsBase &
32
+ (SegmentedControlViewProps | SegmentedControlInputProps);
33
+
34
+ const SegmentedControl = ({
35
+ name,
36
+ defaultValue,
37
+ mode = 'input',
38
+ segments,
39
+ onChange,
40
+ }: SegmentedControlProps) => {
41
+ const [selectedValue, setSelectedValue] = useState(defaultValue || segments[0].value);
42
+ const [animate, setAnimate] = useState(false);
43
+
44
+ const segmentsRef = useRef<HTMLDivElement>(null);
45
+
46
+ if (segments.length > 3) {
47
+ throw new Error(
48
+ 'SegmentedControl only supports up to 3 segments. Please refer to: https://wise.design/components/segmented-control',
49
+ );
50
+ }
51
+
52
+ const segmentsWithRefs = segments.map((segment) => ({
53
+ ...segment,
54
+ ref: createRef<HTMLLabelElement | HTMLButtonElement>(),
55
+ }));
56
+
57
+ const updateSegmentPosition = () => {
58
+ const selectedSegmentRef = segmentsWithRefs.find(
59
+ (segment) => segment.value === selectedValue,
60
+ )?.ref;
61
+
62
+ // We grab the active segments style object from the ref
63
+ // and set the css variables to the selected segments width and x position.
64
+ // This is so we can animate the highlight to the selected segment
65
+ if (selectedSegmentRef?.current && segmentsRef.current) {
66
+ const { style } = segmentsRef.current;
67
+ style.setProperty('--segment-highlight-width', `${selectedSegmentRef.current.offsetWidth}px`);
68
+ style.setProperty('--segment-highlight-x', `${selectedSegmentRef.current.offsetLeft}px`);
69
+ }
70
+ };
71
+
72
+ useEffect(() => {
73
+ updateSegmentPosition();
74
+
75
+ const handleWindowSizeChange = () => {
76
+ setAnimate(false);
77
+ updateSegmentPosition();
78
+ };
79
+
80
+ window.addEventListener('resize', handleWindowSizeChange);
81
+ return () => {
82
+ window.removeEventListener('resize', handleWindowSizeChange);
83
+ };
84
+
85
+ // eslint-disable-next-line react-hooks/exhaustive-deps
86
+ }, [segmentsWithRefs, selectedValue]);
87
+
88
+ useEffect(() => {
89
+ onChange(selectedValue);
90
+ }, [onChange, selectedValue]);
91
+
92
+ return (
93
+ <div
94
+ ref={segmentsRef}
95
+ data-testid="segmented-control"
96
+ className={classNames('segmented-control', {
97
+ 'segmented-control--input': mode === 'input',
98
+ })}
99
+ >
100
+ <div
101
+ className={classNames('segmented-control__segments', {
102
+ 'segmented-control__segments--no-animate': !animate,
103
+ })}
104
+ >
105
+ {segmentsWithRefs.map((segment) =>
106
+ mode === 'input' ? (
107
+ <label
108
+ ref={segment.ref as React.RefObject<HTMLLabelElement>}
109
+ key={segment.id}
110
+ htmlFor={segment.id}
111
+ className={classNames('segmented-control__segment', {
112
+ 'segmented-control__selected-segment': selectedValue === segment.value,
113
+ })}
114
+ >
115
+ <input
116
+ type="radio"
117
+ className="segmented-control__radio-input"
118
+ id={segment.id}
119
+ name={name}
120
+ value={segment.value}
121
+ checked={selectedValue === segment.value}
122
+ onChange={() => {
123
+ setAnimate(true);
124
+ setSelectedValue(segment.value);
125
+ }}
126
+ />
127
+ <Body
128
+ className="segmented-control__text"
129
+ as="span"
130
+ type={
131
+ selectedValue === segment.value
132
+ ? Typography.BODY_DEFAULT_BOLD
133
+ : Typography.BODY_DEFAULT
134
+ }
135
+ >
136
+ {segment.label}
137
+ </Body>
138
+ </label>
139
+ ) : (
140
+ <button
141
+ ref={segment.ref as React.RefObject<HTMLButtonElement>}
142
+ key={segment.id}
143
+ type="button"
144
+ role="tab"
145
+ id={segment.id}
146
+ aria-controls={segment.controls}
147
+ aria-selected={selectedValue === segment.value}
148
+ className={classNames('segmented-control__segment', 'segmented-control__button', {
149
+ 'segmented-control__selected-segment': selectedValue === segment.value,
150
+ })}
151
+ onClick={() => {
152
+ setAnimate(true);
153
+ setSelectedValue(segment.value);
154
+ }}
155
+ >
156
+ <Body
157
+ as="span"
158
+ className="segmented-control__text"
159
+ type={
160
+ selectedValue === segment.value
161
+ ? Typography.BODY_DEFAULT_BOLD
162
+ : Typography.BODY_DEFAULT
163
+ }
164
+ >
165
+ {segment.label}
166
+ </Body>
167
+ </button>
168
+ ),
169
+ )}
170
+ </div>
171
+ </div>
172
+ );
173
+ };
174
+
175
+ export default SegmentedControl;
@@ -0,0 +1,2 @@
1
+ export * from './SegmentedControl';
2
+ export { default } from './SegmentedControl';
@@ -152,10 +152,6 @@
152
152
  border-radius: var(--radius-small);
153
153
  color: #37517e;
154
154
  color: var(--color-content-primary);
155
- line-height: 1.5;
156
- line-height: var(--line-height-body);
157
- font-size: 1rem;
158
- font-size: var(--font-size-16);
159
155
  font-size: 0.875rem;
160
156
  font-size: var(--font-size-14);
161
157
  line-height: 155%;
package/src/ssr.spec.js CHANGED
@@ -154,6 +154,23 @@ describe('Server side rendering', () => {
154
154
  label: 'Two',
155
155
  },
156
156
  ],
157
+ segments: [
158
+ {
159
+ id: '1',
160
+ value: 'accounting',
161
+ label: 'Accounting',
162
+ },
163
+ {
164
+ id: '2',
165
+ value: 'payroll',
166
+ label: 'Payroll',
167
+ },
168
+ {
169
+ id: '3',
170
+ value: 'reporting',
171
+ label: 'Reporting',
172
+ },
173
+ ],
157
174
  };
158
175
 
159
176
  // Override props in case of name collision.
@@ -145,7 +145,7 @@ describe('InputWithTextFormat', () => {
145
145
  });
146
146
 
147
147
  describe('set cursor position', () => {
148
- const triggerEventA = { ...triggerEvent, currentTarget: { setSelectionRange: () => {} } };
148
+ const triggerEventA = { ...triggerEvent, target: { setSelectionRange: () => {} } };
149
149
  beforeEach(() => {
150
150
  component.setState({
151
151
  triggerEvent: triggerEventA,
@@ -284,7 +284,7 @@ class WithDisplayFormat<T extends TextElementProps> extends Component<
284
284
 
285
285
  setTimeout(() => {
286
286
  if (triggerEvent) {
287
- triggerEvent.currentTarget.setSelectionRange(cursorPosition, cursorPosition);
287
+ (triggerEvent.target as HTMLTextElement).setSelectionRange(cursorPosition, cursorPosition);
288
288
  }
289
289
  this.setState({ selectionStart: cursorPosition, selectionEnd: cursorPosition });
290
290
  }, 0);
@@ -1,2 +0,0 @@
1
- export function filterOptionsForQuery(options: any, query: any): any;
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/phoneNumberInput/utils/filterOptionsForQuery/index.js"],"names":[],"mappings":"AASO,qEAC0D"}
@@ -1,2 +0,0 @@
1
- export { isOptionAndFitsQuery, startsWith } from "./isOptionAndFitsQuery";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js"],"names":[],"mappings":""}
@@ -1,3 +0,0 @@
1
- export function isOptionAndFitsQuery(option: object, query: string): boolean;
2
- export function startsWith(property: any, query: any): boolean;
3
- //# sourceMappingURL=isOptionAndFitsQuery.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"isOptionAndFitsQuery.d.ts","sourceRoot":"","sources":["../../../../../src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js"],"names":[],"mappings":"AAQO,6CAJI,MAAM,SACN,MAAM,GACJ,OAAO,CAMa;AAE1B,+DAQN"}
@@ -1,3 +0,0 @@
1
- /// <reference types="react" />
2
- export declare function wrapInFragment(value: unknown): import("react").JSX.Element;
3
- //# sourceMappingURL=wrapInFragment.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wrapInFragment.d.ts","sourceRoot":"","sources":["../../../src/utilities/wrapInFragment.tsx"],"names":[],"mappings":";AAAA,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,+BAE5C"}