@transferwise/components 0.0.0-experimental-53c607c → 0.0.0-experimental-b002e6f

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 (130) 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 -74
  6. package/build/button/Button.js.map +1 -1
  7. package/build/button/Button.mjs +98 -75
  8. package/build/button/Button.mjs.map +1 -1
  9. package/build/common/action/Action.js +1 -1
  10. package/build/common/action/Action.js.map +1 -1
  11. package/build/common/action/Action.mjs +1 -1
  12. package/build/common/action/Action.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/index.js +2 -2
  22. package/build/index.mjs +1 -1
  23. package/build/link/Link.js +3 -8
  24. package/build/link/Link.js.map +1 -1
  25. package/build/link/Link.mjs +3 -8
  26. package/build/link/Link.mjs.map +1 -1
  27. package/build/main.css +0 -269
  28. package/build/nudge/Nudge.js +1 -1
  29. package/build/nudge/Nudge.js.map +1 -1
  30. package/build/nudge/Nudge.mjs +1 -1
  31. package/build/nudge/Nudge.mjs.map +1 -1
  32. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js +3 -1
  33. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js.map +1 -1
  34. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs +3 -1
  35. package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs.map +1 -1
  36. package/build/primitives/PrimitiveButton/src/PrimitiveButton.js +4 -1
  37. package/build/primitives/PrimitiveButton/src/PrimitiveButton.js.map +1 -1
  38. package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs +4 -1
  39. package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs.map +1 -1
  40. package/build/select/Select.js +3 -5
  41. package/build/select/Select.js.map +1 -1
  42. package/build/select/Select.mjs +2 -4
  43. package/build/select/Select.mjs.map +1 -1
  44. package/build/styles/avatarLayout/AvatarLayout.css +0 -11
  45. package/build/styles/button/Button.css +16 -251
  46. package/build/styles/main.css +0 -269
  47. package/build/types/avatarLayout/AvatarLayout.d.ts +2 -1
  48. package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
  49. package/build/types/avatarLayout/index.d.ts +0 -1
  50. package/build/types/avatarLayout/index.d.ts.map +1 -1
  51. package/build/types/button/Button.d.ts +23 -1
  52. package/build/types/button/Button.d.ts.map +1 -1
  53. package/build/types/button/index.d.ts +2 -2
  54. package/build/types/button/index.d.ts.map +1 -1
  55. package/build/types/definitionList/DefinitionList.d.ts +1 -2
  56. package/build/types/definitionList/DefinitionList.d.ts.map +1 -1
  57. package/build/types/link/Link.d.ts +2 -2
  58. package/build/types/link/Link.d.ts.map +1 -1
  59. package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.d.ts.map +1 -1
  60. package/build/types/primitives/PrimitiveButton/src/PrimitiveButton.d.ts.map +1 -1
  61. package/build/types/select/Select.d.ts.map +1 -1
  62. package/build/types/test-utils/index.d.ts.map +1 -1
  63. package/build/upload/steps/completeStep/completeStep.js +2 -2
  64. package/build/upload/steps/completeStep/completeStep.js.map +1 -1
  65. package/build/upload/steps/completeStep/completeStep.mjs +1 -1
  66. package/build/upload/steps/processingStep/processingStep.js +2 -2
  67. package/build/upload/steps/processingStep/processingStep.js.map +1 -1
  68. package/build/upload/steps/processingStep/processingStep.mjs +1 -1
  69. package/build/uploadInput/UploadInput.js +3 -3
  70. package/build/uploadInput/UploadInput.js.map +1 -1
  71. package/build/uploadInput/UploadInput.mjs +1 -1
  72. package/package.json +3 -3
  73. package/src/avatarLayout/AvatarLayout.css +0 -11
  74. package/src/avatarLayout/AvatarLayout.less +1 -18
  75. package/src/avatarLayout/AvatarLayout.tsx +3 -11
  76. package/src/avatarLayout/index.ts +0 -1
  77. package/src/avatarWrapper/AvatarWrapper.story.tsx +0 -2
  78. package/src/button/Button.css +16 -251
  79. package/src/button/Button.less +14 -214
  80. package/src/button/Button.spec.tsx +231 -54
  81. package/src/button/Button.story.tsx +136 -287
  82. package/src/button/Button.tsx +131 -86
  83. package/src/button/__snapshots__/{LegacyButton.spec.tsx.snap → Button.spec.tsx.snap} +22 -22
  84. package/src/button/index.ts +3 -2
  85. package/src/common/action/Action.tsx +1 -1
  86. package/src/definitionList/DefinitionList.story.tsx +57 -57
  87. package/src/definitionList/DefinitionList.tsx +1 -1
  88. package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +1 -1
  89. package/src/iconButton/IconButton.less +1 -1
  90. package/src/inputs/SelectInput.story.tsx +1 -1
  91. package/src/link/Link.tsx +6 -15
  92. package/src/main.css +0 -269
  93. package/src/main.less +0 -1
  94. package/src/nudge/Nudge.tsx +1 -1
  95. package/src/primitives/PrimitiveAnchor/src/PrimitiveAnchor.tsx +7 -1
  96. package/src/primitives/PrimitiveAnchor/stories/PrimitiveAnchor.story.tsx +1 -1
  97. package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +3 -1
  98. package/src/primitives/PrimitiveButton/src/PrimitiveButton.tsx +8 -1
  99. package/src/primitives/PrimitiveButton/stories/PrimitiveButton.story.tsx +1 -1
  100. package/src/primitives/PrimitiveButton/test/PrimitiveButton.spec.tsx +5 -2
  101. package/src/select/Select.tsx +0 -1
  102. package/src/slidingPanel/SlidingPanel.spec.tsx +69 -0
  103. package/src/test-utils/index.tsx +1 -0
  104. package/build/button/Button.resolver.js +0 -67
  105. package/build/button/Button.resolver.js.map +0 -1
  106. package/build/button/Button.resolver.mjs +0 -65
  107. package/build/button/Button.resolver.mjs.map +0 -1
  108. package/build/button/LegacyButton.js +0 -114
  109. package/build/button/LegacyButton.js.map +0 -1
  110. package/build/button/LegacyButton.mjs +0 -112
  111. package/build/button/LegacyButton.mjs.map +0 -1
  112. package/build/styles/button/Button.vars.css +0 -57
  113. package/build/styles/button/LegacyButton.css +0 -23
  114. package/build/types/button/Button.resolver.d.ts +0 -31
  115. package/build/types/button/Button.resolver.d.ts.map +0 -1
  116. package/build/types/button/Button.types.d.ts +0 -50
  117. package/build/types/button/Button.types.d.ts.map +0 -1
  118. package/build/types/button/LegacyButton.d.ts +0 -30
  119. package/build/types/button/LegacyButton.d.ts.map +0 -1
  120. package/src/button/Button.resolver.tsx +0 -113
  121. package/src/button/Button.types.ts +0 -70
  122. package/src/button/Button.vars.css +0 -57
  123. package/src/button/Button.vars.less +0 -71
  124. package/src/button/LegacyButton.css +0 -23
  125. package/src/button/LegacyButton.less +0 -24
  126. package/src/button/LegacyButton.spec.tsx +0 -245
  127. package/src/button/LegacyButton.story.tsx +0 -224
  128. package/src/button/LegacyButton.tsx +0 -161
  129. package/src/definitionList/DefinitionList.spec.js +0 -91
  130. package/src/slidingPanel/SlidingPanel.spec.js +0 -56
@@ -1,314 +1,163 @@
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',
15
- argTypes: {
16
- as: {
17
- type: {
18
- name: 'enum',
19
- value: ['button', 'a'],
20
- },
21
- },
22
- size: {
23
- type: {
24
- name: 'enum',
25
- value: ['sm', 'md', 'lg'],
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
- appearance: {
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
12
  args: {
66
13
  children: 'Button text',
67
- as: 'button',
68
- href: undefined,
69
- disabled: false,
70
14
  loading: false,
71
- size: 'md',
72
- iconStart: undefined,
73
- iconEnd: undefined,
74
- priority: 'primary',
75
- appearance: 'default',
76
- avatars: undefined,
77
- useNewButton: true,
78
15
  onClick: fn(),
79
16
  onBlur: fn(),
80
17
  onFocus: fn(),
81
- onKeyDown: fn(),
82
- onMouseEnter: fn(),
83
- onMouseLeave: fn(),
84
- },
85
- tags: ['autodocs'],
86
- decorators: [withContainer],
87
- };
88
-
89
- export default meta;
90
-
91
- type Story = StoryObj<typeof Button>;
92
-
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
- appearance: 'negative',
145
- },
146
- parameters: {
147
- docs: {
148
- description: {
149
- story: 'A Button with negative appearance.',
150
- },
151
- },
152
- },
153
- };
154
-
155
- export const NegativeSecondary: Story = {
156
- args: {
157
- appearance: 'negative',
158
- priority: 'secondary',
159
18
  },
160
- parameters: {
161
- docs: {
162
- description: {
163
- story: 'A Button with negative appearance and secondary priority.',
164
- },
165
- },
166
- },
167
- };
168
-
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.',
178
- },
179
- },
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.',
19
+ argTypes: {
20
+ as: {
21
+ type: {
22
+ name: 'enum',
23
+ value: ['button', 'a'],
191
24
  },
192
25
  },
193
26
  },
194
- };
27
+ tags: ['autodocs'],
28
+ } satisfies Meta<typeof Button>;
195
29
 
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
- };
30
+ type Story = StoryObj<typeof Button>;
208
31
 
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
- };
32
+ export const Basic: Story = {};
221
33
 
222
- export const SizeLarge: Story = {
223
- args: {
224
- size: 'lg',
225
- },
226
- parameters: {
227
- docs: {
228
- description: {
229
- story: 'A large Button.',
34
+ export const Focused = storyConfig<Story>(
35
+ {
36
+ parameters: {
37
+ chromatic: {
38
+ delay: 1000,
230
39
  },
231
40
  },
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,109 +1,154 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
- import { forwardRef, useMemo } 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 = 'md',
16
- href,
17
- disabled = false,
18
- priority = 'primary',
19
- appearance = '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 = useMemo(() => {
31
- return size === 'sm' ? 'small' : size === 'md' ? 'medium' : size === 'lg' ? 'large' : '';
32
- }, [size]);
73
+ const intl = useIntl();
33
74
 
34
- const classNames = clsx(
35
- '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}`,
36
83
  {
37
- [`wds-Button`]: as === 'a',
38
- [`wds-Button--block`]: block,
39
- [`wds-Button--disabled`]: disabled,
40
- [`wds-Button--loading`]: loading,
41
- [`wds-Button--${sizeClass}`]: size,
42
- [`wds-Button--${priority}`]: priority,
43
- [`wds-Button--${appearance}`]: appearance,
84
+ 'btn-loading': loading,
85
+ 'btn-block np-btn-block': block,
86
+ disabled,
44
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],
45
94
  className,
46
95
  );
47
96
 
48
- const contentClassNames = clsx('wds-Button-content', {
49
- [`wds-Button-content--loading`]: loading,
50
- });
97
+ function processIndicatorSize() {
98
+ return ['sm', 'xs'].includes(size) ? 'xxs' : 'xs';
99
+ }
51
100
 
52
- const content = (
53
- <span className={contentClassNames}>
54
- {loading && (
55
- <ProcessIndicator
56
- size="xs"
57
- className="wds-Button-loader"
58
- data-testid="button-loader-indicator"
59
- />
60
- )}
61
- <span className="wds-Button-label" aria-hidden={loading}>
62
- {size === 'lg' ? (
63
- children
64
- ) : (
65
- <>
66
- {size === 'md' && avatars && (
67
- <span className="wds-Button-avatars">
68
- <AvatarLayout orientation="horizontal" avatars={avatars} size={24} />
69
- </span>
70
- )}
71
- {!avatars && IconStart && (
72
- <IconStart className="wds-Button-icon wds-Button-icon--start" />
73
- )}
74
- {children}
75
- {IconEnd && <IconEnd className="wds-Button-icon wds-Button-icon--end" />}
76
- </>
77
- )}
78
- </span>
79
- </span>
80
- );
101
+ const Element = (component as ElementType) ?? 'button';
102
+ let props;
81
103
 
82
- if (as === 'a') {
83
- return (
84
- <PrimitiveAnchor
85
- ref={ref as React.Ref<HTMLAnchorElement>}
86
- {...(props as any)}
87
- href={href}
88
- className={classNames}
89
- disabled={disabled}
90
- >
91
- {content}
92
- </PrimitiveAnchor>
93
- );
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;
94
117
  }
95
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
+
96
133
  return (
97
- <PrimitiveButton
98
- ref={ref as React.Ref<HTMLButtonElement>}
99
- {...(props as any)}
100
- className={classNames}
101
- disabled={disabled}
102
- loading={loading}
103
- 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']}
104
142
  >
105
- {content}
106
- </PrimitiveButton>
143
+ {children}
144
+ {loading && (
145
+ <ProcessIndicator
146
+ size={processIndicatorSize()}
147
+ className="btn-loader"
148
+ data-testid="ButtonProgressIndicator"
149
+ />
150
+ )}
151
+ </Element>
107
152
  );
108
153
  },
109
154
  );