@transferwise/components 0.0.0-experimental-9e19bae → 0.0.0-experimental-b762045

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/avatarLayout/AvatarLayout.js +2 -9
  2. package/build/avatarLayout/AvatarLayout.js.map +1 -1
  3. package/build/avatarLayout/AvatarLayout.mjs +2 -9
  4. package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
  5. package/build/button/Button.js +97 -76
  6. package/build/button/Button.js.map +1 -1
  7. package/build/button/Button.mjs +97 -76
  8. package/build/button/Button.mjs.map +1 -1
  9. package/build/circularButton/CircularButton.js +19 -24
  10. package/build/circularButton/CircularButton.js.map +1 -1
  11. package/build/circularButton/CircularButton.mjs +20 -25
  12. package/build/circularButton/CircularButton.mjs.map +1 -1
  13. package/build/criticalBanner/CriticalCommsBanner.js +2 -2
  14. package/build/criticalBanner/CriticalCommsBanner.js.map +1 -1
  15. package/build/criticalBanner/CriticalCommsBanner.mjs +1 -1
  16. package/build/definitionList/DefinitionList.js.map +1 -1
  17. package/build/definitionList/DefinitionList.mjs.map +1 -1
  18. package/build/header/Header.js +2 -2
  19. package/build/header/Header.js.map +1 -1
  20. package/build/header/Header.mjs +1 -1
  21. package/build/i18n/de.json +1 -0
  22. package/build/i18n/de.json.js +1 -0
  23. package/build/i18n/de.json.js.map +1 -1
  24. package/build/i18n/de.json.mjs +1 -0
  25. package/build/i18n/de.json.mjs.map +1 -1
  26. package/build/i18n/it.json +1 -0
  27. package/build/i18n/it.json.js +1 -0
  28. package/build/i18n/it.json.js.map +1 -1
  29. package/build/i18n/it.json.mjs +1 -0
  30. package/build/i18n/it.json.mjs.map +1 -1
  31. package/build/i18n/th.json +1 -0
  32. package/build/i18n/th.json.js +1 -0
  33. package/build/i18n/th.json.js.map +1 -1
  34. package/build/i18n/th.json.mjs +1 -0
  35. package/build/i18n/th.json.mjs.map +1 -1
  36. package/build/index.js +2 -2
  37. package/build/index.mjs +1 -1
  38. package/build/link/Link.js +3 -8
  39. package/build/link/Link.js.map +1 -1
  40. package/build/link/Link.mjs +3 -8
  41. package/build/link/Link.mjs.map +1 -1
  42. package/build/main.css +17 -431
  43. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js +3 -1
  44. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js.map +1 -1
  45. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs +3 -1
  46. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs.map +1 -1
  47. package/build/primitives/PrimitiveButton/src/PrimitiveButton.js +4 -1
  48. package/build/primitives/PrimitiveButton/src/PrimitiveButton.js.map +1 -1
  49. package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs +4 -1
  50. package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs.map +1 -1
  51. package/build/select/Select.js +3 -5
  52. package/build/select/Select.js.map +1 -1
  53. package/build/select/Select.mjs +2 -4
  54. package/build/select/Select.mjs.map +1 -1
  55. package/build/styles/avatarLayout/AvatarLayout.css +0 -11
  56. package/build/styles/button/Button.css +16 -255
  57. package/build/styles/circularButton/CircularButton.css +17 -158
  58. package/build/styles/main.css +17 -431
  59. package/build/types/avatarLayout/AvatarLayout.d.ts +2 -1
  60. package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
  61. package/build/types/avatarLayout/index.d.ts +0 -1
  62. package/build/types/avatarLayout/index.d.ts.map +1 -1
  63. package/build/types/button/Button.d.ts +23 -1
  64. package/build/types/button/Button.d.ts.map +1 -1
  65. package/build/types/button/index.d.ts +2 -2
  66. package/build/types/button/index.d.ts.map +1 -1
  67. package/build/types/circularButton/CircularButton.d.ts +11 -4
  68. package/build/types/circularButton/CircularButton.d.ts.map +1 -1
  69. package/build/types/definitionList/DefinitionList.d.ts +1 -2
  70. package/build/types/definitionList/DefinitionList.d.ts.map +1 -1
  71. package/build/types/link/Link.d.ts +2 -2
  72. package/build/types/link/Link.d.ts.map +1 -1
  73. package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.d.ts.map +1 -1
  74. package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.d.ts +1 -1
  75. package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.d.ts.map +1 -1
  76. package/build/types/primitives/PrimitiveButton/src/PrimitiveButton.d.ts.map +1 -1
  77. package/build/types/select/Select.d.ts.map +1 -1
  78. package/build/upload/steps/completeStep/completeStep.js +2 -2
  79. package/build/upload/steps/completeStep/completeStep.js.map +1 -1
  80. package/build/upload/steps/completeStep/completeStep.mjs +1 -1
  81. package/build/upload/steps/processingStep/processingStep.js +2 -2
  82. package/build/upload/steps/processingStep/processingStep.js.map +1 -1
  83. package/build/upload/steps/processingStep/processingStep.mjs +1 -1
  84. package/build/uploadInput/UploadInput.js +3 -3
  85. package/build/uploadInput/UploadInput.js.map +1 -1
  86. package/build/uploadInput/UploadInput.mjs +1 -1
  87. package/package.json +3 -3
  88. package/src/avatarLayout/AvatarLayout.css +0 -11
  89. package/src/avatarLayout/AvatarLayout.less +1 -18
  90. package/src/avatarLayout/AvatarLayout.tsx +3 -11
  91. package/src/avatarLayout/index.ts +0 -1
  92. package/src/button/Button.css +16 -255
  93. package/src/button/Button.less +14 -215
  94. package/src/button/Button.spec.tsx +231 -54
  95. package/src/button/Button.story.tsx +136 -287
  96. package/src/button/Button.tsx +131 -84
  97. package/src/button/__snapshots__/{LegacyButton.spec.tsx.snap → Button.spec.tsx.snap} +22 -22
  98. package/src/button/index.ts +3 -2
  99. package/src/circularButton/CircularButton.css +17 -158
  100. package/src/circularButton/CircularButton.less +22 -91
  101. package/src/circularButton/CircularButton.story.tsx +45 -24
  102. package/src/circularButton/CircularButton.tsx +39 -28
  103. package/src/definitionList/DefinitionList.story.tsx +57 -57
  104. package/src/definitionList/DefinitionList.tsx +1 -1
  105. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +1 -1
  106. package/src/i18n/de.json +1 -0
  107. package/src/i18n/it.json +1 -0
  108. package/src/i18n/th.json +1 -0
  109. package/src/iconButton/IconButton.story.tsx +6 -6
  110. package/src/inputs/SelectInput.story.tsx +1 -1
  111. package/src/link/Link.tsx +6 -15
  112. package/src/main.css +17 -431
  113. package/src/main.less +0 -1
  114. package/src/primitives/PrimitiveAnchor/src/PrimitiveAnchor.tsx +7 -1
  115. package/src/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.ts +1 -1
  116. package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +3 -1
  117. package/src/primitives/PrimitiveButton/src/PrimitiveButton.tsx +8 -1
  118. package/src/primitives/PrimitiveButton/test/PrimitiveButton.spec.tsx +5 -2
  119. package/src/select/Select.tsx +0 -1
  120. package/src/slidingPanel/SlidingPanel.spec.tsx +69 -0
  121. package/build/button/Button.resolver.js +0 -78
  122. package/build/button/Button.resolver.js.map +0 -1
  123. package/build/button/Button.resolver.mjs +0 -76
  124. package/build/button/Button.resolver.mjs.map +0 -1
  125. package/build/button/LegacyButton.js +0 -114
  126. package/build/button/LegacyButton.js.map +0 -1
  127. package/build/button/LegacyButton.mjs +0 -112
  128. package/build/button/LegacyButton.mjs.map +0 -1
  129. package/build/styles/button/Button.vars.css +0 -59
  130. package/build/styles/button/LegacyButton.css +0 -23
  131. package/build/types/button/Button.resolver.d.ts +0 -31
  132. package/build/types/button/Button.resolver.d.ts.map +0 -1
  133. package/build/types/button/Button.types.d.ts +0 -58
  134. package/build/types/button/Button.types.d.ts.map +0 -1
  135. package/build/types/button/LegacyButton.d.ts +0 -30
  136. package/build/types/button/LegacyButton.d.ts.map +0 -1
  137. package/src/button/Button.resolver.tsx +0 -120
  138. package/src/button/Button.types.ts +0 -79
  139. package/src/button/Button.vars.css +0 -59
  140. package/src/button/Button.vars.less +0 -73
  141. package/src/button/LegacyButton.css +0 -23
  142. package/src/button/LegacyButton.less +0 -24
  143. package/src/button/LegacyButton.spec.tsx +0 -245
  144. package/src/button/LegacyButton.story.tsx +0 -224
  145. package/src/button/LegacyButton.tsx +0 -161
  146. package/src/circularButton/_button-label-states.less +0 -34
  147. package/src/definitionList/DefinitionList.spec.js +0 -91
  148. package/src/slidingPanel/SlidingPanel.spec.js +0 -56
@@ -1,17 +1,21 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
- import { fn } from '@storybook/test';
3
- import Button from './Button.resolver';
4
- import { Freeze, ArrowRight } from '@transferwise/icons';
2
+ import { userEvent, within, fn } from '@storybook/test';
5
3
 
6
- const withContainer = (Story: any) => (
7
- <div style={{ display: 'flex', flexDirection: 'initial', justifyContent: 'center' }}>
8
- <Story />
9
- </div>
10
- );
4
+ import { ControlType, Priority } from '../common';
5
+ import { storyConfig } from '../test-utils';
6
+
7
+ import Button from './Button';
11
8
 
12
- const meta: Meta<typeof Button> = {
9
+ export default {
13
10
  component: Button,
14
11
  title: 'Actions/Button',
12
+ args: {
13
+ children: 'Button text',
14
+ loading: false,
15
+ onClick: fn(),
16
+ onBlur: fn(),
17
+ onFocus: fn(),
18
+ },
15
19
  argTypes: {
16
20
  as: {
17
21
  type: {
@@ -19,296 +23,141 @@ const meta: Meta<typeof Button> = {
19
23
  value: ['button', 'a'],
20
24
  },
21
25
  },
22
- size: {
23
- type: {
24
- name: 'enum',
25
- value: ['lg', 'md', 'sm'],
26
- },
27
- },
28
- href: {
29
- type: {
30
- name: 'string',
31
- },
32
- },
33
- priority: {
34
- type: {
35
- name: 'enum',
36
- value: ['primary', 'secondary', 'tertiary', 'minimal'],
37
- },
38
- },
39
- sentiment: {
40
- type: {
41
- name: 'enum',
42
- value: ['default', 'negative'],
43
- },
44
- },
45
- iconStart: {
46
- control: 'object',
47
- },
48
- iconEnd: {
49
- control: 'object',
50
- },
51
- avatars: {
52
- control: 'object',
53
- },
54
- htmlType: {
55
- table: {
56
- disable: true,
57
- },
58
- },
59
- 'data-testid': {
60
- table: {
61
- disable: true,
62
- },
63
- },
64
- },
65
- args: {
66
- v2: true,
67
- as: 'button',
68
- size: 'lg',
69
- priority: 'primary',
70
- sentiment: 'default',
71
- disabled: false,
72
- loading: false,
73
- href: undefined,
74
- iconStart: undefined,
75
- iconEnd: undefined,
76
- avatars: undefined,
77
- onClick: fn(),
78
- onBlur: fn(),
79
- onFocus: fn(),
80
- onKeyDown: fn(),
81
- onMouseEnter: fn(),
82
- onMouseLeave: fn(),
83
- children: 'Button text',
84
26
  },
85
27
  tags: ['autodocs'],
86
- decorators: [withContainer],
87
- };
88
-
89
- export default meta;
28
+ } satisfies Meta<typeof Button>;
90
29
 
91
30
  type Story = StoryObj<typeof Button>;
92
31
 
93
- export const Basic: Story = {
94
- parameters: {
95
- docs: {
96
- description: {
97
- story: 'A basic example of the Button component.',
98
- },
99
- },
100
- },
101
- };
102
-
103
- export const Secondary: Story = {
104
- args: {
105
- priority: 'secondary',
106
- },
107
- parameters: {
108
- docs: {
109
- description: {
110
- story: 'A Button with secondary priority.',
111
- },
112
- },
113
- },
114
- };
115
-
116
- export const Tertiary: Story = {
117
- args: {
118
- priority: 'tertiary',
119
- },
120
- parameters: {
121
- docs: {
122
- description: {
123
- story: 'A Button with tertiary priority.',
124
- },
125
- },
126
- },
127
- };
128
-
129
- export const Minimal: Story = {
130
- args: {
131
- priority: 'minimal',
132
- },
133
- parameters: {
134
- docs: {
135
- description: {
136
- story: 'A Button with minimal priority.',
137
- },
138
- },
139
- },
140
- };
141
-
142
- export const Negative: Story = {
143
- args: {
144
- sentiment: 'negative',
145
- },
146
- parameters: {
147
- docs: {
148
- description: {
149
- story: 'A Button with negative sentiment.',
150
- },
151
- },
152
- },
153
- };
154
-
155
- export const NegativeSecondary: Story = {
156
- args: {
157
- sentiment: 'negative',
158
- priority: 'secondary',
159
- },
160
- parameters: {
161
- docs: {
162
- description: {
163
- story: 'A Button with negative sentiment and secondary priority.',
164
- },
165
- },
166
- },
167
- };
32
+ export const Basic: Story = {};
168
33
 
169
- export const AsAnchor: Story = {
170
- args: {
171
- as: 'a',
172
- href: 'https://wise.com',
173
- },
174
- parameters: {
175
- docs: {
176
- description: {
177
- story: 'A Button rendered as an anchor element.',
34
+ export const Focused = storyConfig<Story>(
35
+ {
36
+ parameters: {
37
+ chromatic: {
38
+ delay: 1000,
178
39
  },
179
40
  },
180
- },
181
- };
182
-
183
- export const Disabled: Story = {
184
- args: {
185
- disabled: true,
186
- },
187
- parameters: {
188
- docs: {
189
- description: {
190
- story: 'A disabled Button.',
191
- },
192
- },
193
- },
194
- };
195
-
196
- export const Loading: Story = {
197
- args: {
198
- loading: true,
199
- },
200
- parameters: {
201
- docs: {
202
- description: {
203
- story: 'A Button in a loading state.',
204
- },
205
- },
206
- },
207
- };
208
-
209
- export const SizeSmall: Story = {
210
- args: {
211
- size: 'sm',
212
- },
213
- parameters: {
214
- docs: {
215
- description: {
216
- story: 'A small Button.',
217
- },
218
- },
219
- },
220
- };
221
-
222
- export const SizeLarge: Story = {
223
- args: {
224
- size: 'lg',
225
- },
226
- parameters: {
227
- docs: {
228
- description: {
229
- story: 'A large Button.',
230
- },
231
- },
232
- },
233
- };
234
-
235
- export const DisplayBlock: Story = {
236
- args: {
237
- block: true,
238
- },
239
- parameters: {
240
- docs: {
241
- description: {
242
- story: 'A Button that takes up the full width of its container.',
243
- },
41
+ play: async ({ canvasElement }) => {
42
+ const canvas = within(canvasElement);
43
+ await userEvent.click(canvas.getByRole('button'));
244
44
  },
245
45
  },
246
- };
247
-
248
- export const WithIcon: Story = {
249
- args: {
250
- iconEnd: ArrowRight,
251
- },
252
- parameters: {
253
- docs: {
254
- description: {
255
- story:
256
- 'A Button with an icon on the right. Can also use `iconStart` to place the icon on the left or have both at the same time.',
257
- },
258
- },
46
+ {
47
+ variants: ['dark'],
259
48
  },
260
- };
49
+ );
261
50
 
262
- export const WithAvatars: Story = {
263
- args: {
264
- avatars: [{ asset: <Freeze /> }, { asset: <Freeze /> }],
265
- },
266
- parameters: {
267
- docs: {
268
- description: {
269
- story:
270
- 'A Button with avatars. Will supersed any `iconStart` property and will always render avatars.',
271
- },
272
- },
273
- },
274
- };
51
+ export const Variants = storyConfig<Story>(
52
+ {
53
+ render: (args) => {
54
+ return (
55
+ <>
56
+ <div className="m-b-2">
57
+ <div className="title-4 m-b-1">Accent</div>
58
+ <div className="d-flex flex-wrap" style={{ gap: 'var(--size-16)' }}>
59
+ <Button {...args} priority={Priority.PRIMARY} type={ControlType.ACCENT} />
60
+ <Button {...args} priority={Priority.SECONDARY} type={ControlType.ACCENT} />
61
+ <Button {...args} priority={Priority.TERTIARY} type={ControlType.ACCENT} />
62
+ </div>
63
+ </div>
64
+ <div className="m-b-2">
65
+ <div className="title-4 m-b-1">Positive</div>
66
+ <div className="d-flex flex-wrap" style={{ gap: 'var(--size-16)' }}>
67
+ <Button {...args} priority={Priority.PRIMARY} type={ControlType.POSITIVE} />
68
+ <Button {...args} priority={Priority.SECONDARY} type={ControlType.POSITIVE} />
69
+ </div>
70
+ </div>
71
+ <div className="m-b-2">
72
+ <div className="title-4 m-b-1">Negative</div>
73
+ <div className="d-flex flex-wrap" style={{ gap: 'var(--size-16)' }}>
74
+ <Button {...args} priority={Priority.PRIMARY} type={ControlType.NEGATIVE} />
75
+ <Button {...args} priority={Priority.SECONDARY} type={ControlType.NEGATIVE} />
76
+ </div>
77
+ </div>
78
+ <div className="m-b-2">
79
+ <div className="title-4 m-b-1">Disabled</div>
80
+ <div className="d-flex flex-wrap" style={{ gap: 'var(--size-16)' }}>
81
+ <Button {...args} priority={Priority.PRIMARY} disabled />
82
+ <Button {...args} priority={Priority.SECONDARY} disabled />
83
+ <Button {...args} priority={Priority.TERTIARY} disabled />
84
+ </div>
85
+ </div>
86
+ </>
87
+ );
88
+ },
89
+ },
90
+ { variants: ['default', 'dark', 'rtl'] },
91
+ );
275
92
 
276
- export const WithProfileName: Story = {
277
- args: {
278
- avatars: [{ profileName: 'John Doe' }],
279
- },
280
- parameters: {
281
- docs: {
282
- description: {
283
- story: 'A Button with a Profile name.',
284
- },
285
- },
286
- },
287
- };
93
+ /**
94
+ * The purpose of the `loading` mode is to convey a message to the
95
+ * user that some asynchronous process has been triggered, likely
96
+ * in response to their previous action.
97
+ *
98
+ * Although it carries some similarities with the `disabled` mode
99
+ * (users cannot activate a loading button), it's not hidden from
100
+ * the keyboard and assistive tech users (users can focus on it,
101
+ * and it's announced by screen readers), and is also much more
102
+ * accessible to sighted users due to its default, high contrast.
103
+ */
104
+ export const Loading = storyConfig<Story>(
105
+ {
106
+ render: (args) => {
107
+ return (
108
+ <div className="d-flex flex-wrap" style={{ gap: 'var(--size-16)' }}>
109
+ <Button {...args} loading priority={Priority.PRIMARY} />
110
+ <Button {...args} loading priority={Priority.SECONDARY} />
111
+ <Button {...args} loading priority={Priority.TERTIARY} />
112
+ <Button {...args} loading priority={Priority.PRIMARY} type={ControlType.NEGATIVE} />
113
+ <Button {...args} loading priority={Priority.SECONDARY} type={ControlType.NEGATIVE} />
114
+ </div>
115
+ );
116
+ },
117
+ args: {
118
+ loading: true,
119
+ },
120
+ },
121
+ { variants: ['default', 'dark', 'rtl'] },
122
+ );
288
123
 
289
- export const WithProfileImage: Story = {
290
- args: {
291
- avatars: [{ imgSrc: '../avatar-rectangle-fox.webp' }],
292
- },
293
- parameters: {
294
- docs: {
295
- description: {
296
- story: 'A Button with a Profile image.',
297
- },
298
- },
299
- },
300
- };
124
+ export const SocialMedia = storyConfig<Story>(
125
+ {
126
+ render: () => {
127
+ return (
128
+ <>
129
+ <div className="m-b-2">
130
+ <button type="button" className="btn btn-google">
131
+ Login with Google
132
+ </button>
133
+ </div>
134
+ <div className="m-b-2">
135
+ <button type="button" className="btn btn-facebook">
136
+ Login with Facebook
137
+ </button>
138
+ </div>
139
+ <div className="m-b-2">
140
+ <button type="button" className="btn btn-lg btn-facebook">
141
+ Large Button
142
+ </button>
143
+ </div>
144
+ <div className="m-b-2">
145
+ <button type="button" className="btn btn-block btn-facebook">
146
+ Block Button
147
+ </button>
148
+ </div>
149
+ <div className="m-b-2">
150
+ <button type="button" className="btn btn-lg btn-block btn-facebook">
151
+ Large Block Button
152
+ </button>
153
+ </div>
154
+ </>
155
+ );
156
+ },
157
+ },
158
+ { variants: ['default', 'dark', 'rtl'] },
159
+ );
301
160
 
302
- export const WithAvatarAndIcon: Story = {
303
- args: {
304
- avatars: [{ asset: <Freeze /> }],
305
- iconEnd: ArrowRight,
306
- },
307
- parameters: {
308
- docs: {
309
- description: {
310
- story: 'A Button with an avatar and an icon.',
311
- },
312
- },
313
- },
314
- };
161
+ export const SocialMediaMobile = storyConfig<Story>(SocialMedia, {
162
+ variants: ['default', 'dark', 'rtl', 'mobile'],
163
+ });
@@ -1,107 +1,154 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
- import { forwardRef } from 'react';
3
- import { ButtonProps as NewButtonProps } from './Button.types';
4
- import { PrimitiveAnchor, PrimitiveButton } from '../primitives';
5
- import AvatarLayout from '../avatarLayout';
6
- import ProcessIndicator from '../processIndicator';
7
1
  import { clsx } from 'clsx';
2
+ import { ElementType, forwardRef, MouseEvent } from 'react';
3
+ import { useIntl } from 'react-intl';
4
+
5
+ import {
6
+ Size,
7
+ ControlType,
8
+ Priority,
9
+ ControlTypeAccent,
10
+ ControlTypeNegative,
11
+ ControlTypePositive,
12
+ PriorityPrimary,
13
+ PrioritySecondary,
14
+ PriorityTertiary,
15
+ SizeExtraSmall,
16
+ SizeSmall,
17
+ SizeMedium,
18
+ SizeLarge,
19
+ } from '../common';
20
+ import ProcessIndicator from '../processIndicator';
21
+
22
+ import messages from '../i18n/commonMessages/Button.messages';
23
+ import { typeClassMap, priorityClassMap } from './classMap';
24
+ import { establishNewPriority, establishNewType, logDeprecationNotices } from './legacyUtils';
25
+
26
+ /** @deprecated */
27
+ type DeprecatedTypes = 'primary' | 'pay' | 'secondary' | 'danger' | 'link';
28
+
29
+ /** @deprecated */
30
+ type DeprecatedSizes = SizeExtraSmall;
31
+
32
+ type CommonProps = {
33
+ block?: boolean;
34
+ disabled?: boolean;
35
+ loading?: boolean;
36
+ type?: ControlTypeAccent | ControlTypeNegative | ControlTypePositive | DeprecatedTypes | null;
37
+ priority?: PriorityPrimary | PrioritySecondary | PriorityTertiary | null;
38
+ size?: SizeSmall | SizeMedium | SizeLarge | DeprecatedSizes;
39
+ };
8
40
 
9
- const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, NewButtonProps>(
41
+ type ButtonProps = CommonProps &
42
+ Omit<React.ComponentPropsWithRef<'button'>, 'type'> & {
43
+ as?: 'button';
44
+ htmlType?: 'submit' | 'reset' | 'button';
45
+ };
46
+
47
+ type AnchorProps = CommonProps &
48
+ Omit<React.ComponentPropsWithRef<'a'>, 'type'> & {
49
+ as?: 'a';
50
+ };
51
+
52
+ export type Props = ButtonProps | AnchorProps;
53
+
54
+ export type ButtonReferenceType = HTMLButtonElement | HTMLAnchorElement;
55
+
56
+ const Button = forwardRef<ButtonReferenceType, Props>(
10
57
  (
11
58
  {
12
- as = 'button',
59
+ as: component,
60
+ block = false,
13
61
  children,
14
62
  className,
15
- size = 'lg',
16
- href,
17
- disabled = false,
18
- priority = 'primary',
19
- sentiment = 'default',
20
- iconStart: IconStart,
21
- iconEnd: IconEnd,
22
- avatars,
23
- type = 'button',
63
+ disabled,
24
64
  loading = false,
25
- block = false,
26
- ...props
27
- },
28
- ref,
65
+ priority = Priority.PRIMARY,
66
+ size = Size.MEDIUM,
67
+ type = ControlType.ACCENT,
68
+ onClick,
69
+ ...rest
70
+ }: Props,
71
+ reference,
29
72
  ) => {
30
- const sizeClass = { sm: 'small', md: 'medium', lg: 'large' }[size];
73
+ const intl = useIntl();
31
74
 
32
- const classNames = clsx(
33
- 'wds-Button',
75
+ logDeprecationNotices({ size, type });
76
+
77
+ const newType = establishNewType(type);
78
+ const newPriority = establishNewPriority(priority, type);
79
+
80
+ const classes = clsx(
81
+ `btn btn-${size}`,
82
+ `np-btn np-btn-${size}`,
34
83
  {
35
- [`wds-Button`]: as === 'a',
36
- [`wds-Button--block`]: block,
37
- [`wds-Button--disabled`]: disabled,
38
- [`wds-Button--loading`]: loading,
39
- [`wds-Button--${sizeClass}`]: size,
40
- [`wds-Button--${priority}`]: priority,
41
- [`wds-Button--${sentiment}`]: sentiment,
84
+ 'btn-loading': loading,
85
+ 'btn-block np-btn-block': block,
86
+ disabled,
42
87
  },
88
+ // @ts-expect-error fix when refactor `typeClassMap` to TypeScript
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
90
+ typeClassMap[newType],
91
+ // @ts-expect-error fix when refactor `typeClassMap` to TypeScript
92
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
93
+ priorityClassMap[newPriority],
43
94
  className,
44
95
  );
45
96
 
46
- const contentClassNames = clsx('wds-Button-content', {
47
- [`wds-Button-content--loading`]: loading,
48
- });
97
+ function processIndicatorSize() {
98
+ return ['sm', 'xs'].includes(size) ? 'xxs' : 'xs';
99
+ }
49
100
 
50
- const content = (
51
- <span className={contentClassNames}>
52
- {loading && (
53
- <ProcessIndicator
54
- size="xs"
55
- className="wds-Button-loader"
56
- data-testid="button-loader-indicator"
57
- />
58
- )}
59
- <span className="wds-Button-label" aria-hidden={loading}>
60
- {size === 'lg' ? (
61
- children
62
- ) : (
63
- <>
64
- {size === 'md' && avatars && (
65
- <span className="wds-Button-avatars">
66
- <AvatarLayout orientation="horizontal" avatars={avatars} size={24} />
67
- </span>
68
- )}
69
- {!avatars && IconStart && (
70
- <IconStart className="wds-Button-icon wds-Button-icon--start" />
71
- )}
72
- {children}
73
- {IconEnd && <IconEnd className="wds-Button-icon wds-Button-icon--end" />}
74
- </>
75
- )}
76
- </span>
77
- </span>
78
- );
101
+ const Element = (component as ElementType) ?? 'button';
102
+ let props;
79
103
 
80
- if (as === 'a') {
81
- return (
82
- <PrimitiveAnchor
83
- ref={ref as React.Ref<HTMLAnchorElement>}
84
- {...(props as any)}
85
- href={href}
86
- className={classNames}
87
- disabled={disabled}
88
- >
89
- {content}
90
- </PrimitiveAnchor>
91
- );
104
+ if (Element === 'button') {
105
+ const { htmlType = 'button', ...restProps } = rest as ButtonProps;
106
+ props = {
107
+ ...restProps,
108
+ disabled,
109
+ 'aria-disabled': loading,
110
+ type: htmlType,
111
+ };
112
+ } else {
113
+ props = {
114
+ ...rest,
115
+ 'aria-disabled': loading,
116
+ } as AnchorProps;
92
117
  }
93
118
 
119
+ /**
120
+ * Ensures that the button cannot be activated in loading or disabled mode,
121
+ * when `aria-disabled` might be used over the `disabled` HTML attribute
122
+ */
123
+ const handleClick =
124
+ (handler: Props['onClick']) =>
125
+ (event: MouseEvent<HTMLButtonElement> & MouseEvent<HTMLAnchorElement>) => {
126
+ if (disabled || loading) {
127
+ event.preventDefault();
128
+ } else if (typeof handler === 'function') {
129
+ handler(event);
130
+ }
131
+ };
132
+
94
133
  return (
95
- <PrimitiveButton
96
- ref={ref as React.Ref<HTMLButtonElement>}
97
- {...(props as any)}
98
- className={classNames}
99
- disabled={disabled}
100
- loading={loading}
101
- type={type}
134
+ <Element
135
+ ref={reference}
136
+ className={classes}
137
+ onClick={handleClick(onClick)}
138
+ {...props}
139
+ aria-live={loading ? 'polite' : 'off'}
140
+ aria-busy={loading}
141
+ aria-label={loading ? intl.formatMessage(messages.loadingAriaLabel) : rest['aria-label']}
102
142
  >
103
- {content}
104
- </PrimitiveButton>
143
+ {children}
144
+ {loading && (
145
+ <ProcessIndicator
146
+ size={processIndicatorSize()}
147
+ className="btn-loader"
148
+ data-testid="ButtonProgressIndicator"
149
+ />
150
+ )}
151
+ </Element>
105
152
  );
106
153
  },
107
154
  );