@transferwise/components 46.87.2 → 46.88.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.
- package/build/avatarLayout/AvatarLayout.js +9 -2
- package/build/avatarLayout/AvatarLayout.js.map +1 -1
- package/build/avatarLayout/AvatarLayout.mjs +9 -2
- package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/button/Button.js +92 -79
- package/build/button/Button.js.map +1 -1
- package/build/button/Button.mjs +93 -80
- package/build/button/Button.mjs.map +1 -1
- package/build/button/Button.resolver.js +74 -0
- package/build/button/Button.resolver.js.map +1 -0
- package/build/button/Button.resolver.mjs +72 -0
- package/build/button/Button.resolver.mjs.map +1 -0
- package/build/button/LegacyButton.js +114 -0
- package/build/button/LegacyButton.js.map +1 -0
- package/build/button/LegacyButton.mjs +112 -0
- package/build/button/LegacyButton.mjs.map +1 -0
- package/build/circularButton/CircularButton.js.map +1 -1
- package/build/circularButton/CircularButton.mjs.map +1 -1
- package/build/criticalBanner/CriticalCommsBanner.js +2 -2
- package/build/criticalBanner/CriticalCommsBanner.js.map +1 -1
- package/build/criticalBanner/CriticalCommsBanner.mjs +1 -1
- package/build/header/Header.js +2 -2
- package/build/header/Header.js.map +1 -1
- package/build/header/Header.mjs +1 -1
- package/build/index.js +2 -2
- package/build/index.mjs +1 -1
- package/build/link/Link.js +8 -3
- package/build/link/Link.js.map +1 -1
- package/build/link/Link.mjs +8 -3
- package/build/link/Link.mjs.map +1 -1
- package/build/main.css +247 -0
- package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js +2 -4
- package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.js.map +1 -1
- package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs +2 -4
- package/build/primitives/PrimitiveAnchor/src/PrimitiveAnchor.mjs.map +1 -1
- package/build/primitives/PrimitiveButton/src/PrimitiveButton.js +3 -5
- package/build/primitives/PrimitiveButton/src/PrimitiveButton.js.map +1 -1
- package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs +3 -5
- package/build/primitives/PrimitiveButton/src/PrimitiveButton.mjs.map +1 -1
- package/build/select/Select.js +2 -2
- package/build/select/Select.js.map +1 -1
- package/build/select/Select.mjs +1 -1
- package/build/styles/avatarLayout/AvatarLayout.css +11 -0
- package/build/styles/button/Button.css +228 -15
- package/build/styles/button/Button.vars.css +46 -0
- package/build/styles/button/LegacyButton.css +23 -0
- package/build/styles/main.css +247 -0
- package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
- package/build/types/button/Button.d.ts +2 -23
- package/build/types/button/Button.d.ts.map +1 -1
- package/build/types/button/Button.resolver.d.ts +35 -0
- package/build/types/button/Button.resolver.d.ts.map +1 -0
- package/build/types/button/Button.types.d.ts +70 -0
- package/build/types/button/Button.types.d.ts.map +1 -0
- package/build/types/button/LegacyButton.d.ts +44 -0
- package/build/types/button/LegacyButton.d.ts.map +1 -0
- package/build/types/button/index.d.ts +2 -2
- package/build/types/button/index.d.ts.map +1 -1
- package/build/types/circularButton/CircularButton.d.ts.map +1 -1
- package/build/types/link/Link.d.ts +2 -2
- package/build/types/link/Link.d.ts.map +1 -1
- package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.d.ts.map +1 -1
- package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.d.ts +6 -2
- package/build/types/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.d.ts.map +1 -1
- package/build/types/primitives/PrimitiveButton/src/PrimitiveButton.d.ts.map +1 -1
- package/build/types/test-utils/story-config.d.ts +1 -1
- package/build/types/test-utils/story-config.d.ts.map +1 -1
- package/build/upload/steps/completeStep/completeStep.js +2 -2
- package/build/upload/steps/completeStep/completeStep.js.map +1 -1
- package/build/upload/steps/completeStep/completeStep.mjs +1 -1
- package/build/upload/steps/processingStep/processingStep.js +2 -2
- package/build/upload/steps/processingStep/processingStep.js.map +1 -1
- package/build/upload/steps/processingStep/processingStep.mjs +1 -1
- package/build/uploadInput/UploadInput.js +3 -3
- package/build/uploadInput/UploadInput.js.map +1 -1
- package/build/uploadInput/UploadInput.mjs +1 -1
- package/package.json +3 -3
- package/src/alert/Alert.tests.story.tsx +1 -1
- package/src/avatar/Avatar.story.tsx +1 -1
- package/src/avatarLayout/AvatarLayout.css +11 -0
- package/src/avatarLayout/AvatarLayout.less +18 -1
- package/src/avatarLayout/AvatarLayout.tsx +10 -2
- package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
- package/src/badge/Badge.story.tsx +1 -1
- package/src/button/Button.accessibility.docs.mdx +103 -0
- package/src/button/Button.css +228 -15
- package/src/button/Button.less +242 -14
- package/src/button/Button.resolver.tsx +73 -0
- package/src/button/Button.spec.tsx +329 -213
- package/src/button/Button.story.tsx +782 -134
- package/src/button/Button.tests.story.tsx +27 -0
- package/src/button/Button.tsx +103 -132
- package/src/button/Button.types.ts +92 -0
- package/src/button/Button.vars.css +46 -0
- package/src/button/Button.vars.less +59 -0
- package/src/button/LegacyButton.css +23 -0
- package/src/button/LegacyButton.less +24 -0
- package/src/button/LegacyButton.spec.tsx +147 -0
- package/src/button/LegacyButton.story.tsx +228 -0
- package/src/button/LegacyButton.tsx +177 -0
- package/src/button/index.ts +2 -3
- package/src/card/Card.story.tsx +6 -1
- package/src/circularButton/CircularButton.tsx +1 -0
- package/src/field/Field.story.tsx +1 -1
- package/src/flowNavigation/__snapshots__/FlowNavigation.spec.js.snap +1 -2
- package/src/inputs/SelectInput.story.tsx +1 -1
- package/src/label/Label.story.tsx +1 -1
- package/src/link/Link.tsx +15 -6
- package/src/main.css +247 -0
- package/src/main.less +1 -0
- package/src/primitives/PrimitiveAnchor/src/PrimitiveAnchor.tsx +2 -8
- package/src/primitives/PrimitiveAnchor/src/PrimitiveAnchor.types.ts +7 -2
- package/src/primitives/PrimitiveAnchor/test/PrimitiveAnchor.spec.tsx +1 -3
- package/src/primitives/PrimitiveButton/src/PrimitiveButton.tsx +4 -12
- package/src/primitives/PrimitiveButton/test/PrimitiveButton.spec.tsx +16 -13
- package/src/select/Select.story.tsx +4 -1
- package/src/test-utils/Parameters.d.ts +9 -1
- package/src/test-utils/story-config.ts +10 -1
- package/src/button/__snapshots__/Button.spec.tsx.snap +0 -309
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Meta } from '@storybook/react';
|
|
2
|
+
import Button from './Button.resolver';
|
|
3
|
+
import { expect, userEvent, within } from '@storybook/test';
|
|
4
|
+
import { storyConfig } from '../test-utils';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Button> = {
|
|
7
|
+
component: Button,
|
|
8
|
+
title: 'Actions/Button/Tests',
|
|
9
|
+
};
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
export const FocusTertiary = storyConfig(
|
|
13
|
+
{
|
|
14
|
+
render: () => (
|
|
15
|
+
<Button v2 priority="tertiary">
|
|
16
|
+
Focus has underline
|
|
17
|
+
</Button>
|
|
18
|
+
),
|
|
19
|
+
},
|
|
20
|
+
{},
|
|
21
|
+
);
|
|
22
|
+
FocusTertiary.play = async ({ canvasElement }) => {
|
|
23
|
+
await userEvent.tab();
|
|
24
|
+
const canvas = within(canvasElement);
|
|
25
|
+
const buttonElement: HTMLButtonElement = canvas.getByRole('button');
|
|
26
|
+
await expect(buttonElement).toHaveFocus();
|
|
27
|
+
};
|
package/src/button/Button.tsx
CHANGED
|
@@ -1,154 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
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';
|
|
1
|
+
/* eslint-disable react/display-name */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { ButtonReferenceType, ButtonProps as NewButtonProps } from './Button.types';
|
|
5
|
+
import { PrimitiveAnchor, PrimitiveButton } from '../primitives';
|
|
6
|
+
import AvatarLayout from '../avatarLayout';
|
|
20
7
|
import ProcessIndicator from '../processIndicator';
|
|
8
|
+
import { clsx } from 'clsx';
|
|
9
|
+
import { Typography } from '../common';
|
|
10
|
+
import Body from '../body';
|
|
21
11
|
|
|
22
|
-
|
|
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
|
-
};
|
|
40
|
-
|
|
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>(
|
|
12
|
+
const Button = forwardRef<ButtonReferenceType, NewButtonProps>(
|
|
57
13
|
(
|
|
58
14
|
{
|
|
59
|
-
as
|
|
60
|
-
block = false,
|
|
15
|
+
as,
|
|
61
16
|
children,
|
|
62
17
|
className,
|
|
63
|
-
|
|
18
|
+
size = 'lg',
|
|
19
|
+
href,
|
|
20
|
+
disabled = false,
|
|
21
|
+
priority = 'primary',
|
|
22
|
+
sentiment = 'default',
|
|
23
|
+
addonStart,
|
|
24
|
+
addonEnd,
|
|
25
|
+
// @ts-expect-error NewButtonProps has `type` prop
|
|
26
|
+
type = 'button',
|
|
64
27
|
loading = false,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}: Props,
|
|
71
|
-
reference,
|
|
28
|
+
block = false,
|
|
29
|
+
v2,
|
|
30
|
+
...props
|
|
31
|
+
},
|
|
32
|
+
ref,
|
|
72
33
|
) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
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
|
+
const classNames = clsx(
|
|
35
|
+
'wds-Button',
|
|
83
36
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
37
|
+
[`wds-Button--block`]: block,
|
|
38
|
+
[`wds-Button--disabled`]: disabled,
|
|
39
|
+
[`wds-Button--loading`]: loading,
|
|
87
40
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// @ts-expect-error fix when refactor `typeClassMap` to TypeScript
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
93
|
-
priorityClassMap[newPriority],
|
|
41
|
+
`wds-Button--${{ sm: 'small', md: 'medium', lg: 'large' }[size]}`,
|
|
42
|
+
`wds-Button--${priority}`,
|
|
43
|
+
`wds-Button--${sentiment}`,
|
|
94
44
|
className,
|
|
95
45
|
);
|
|
96
46
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const Element = (component as ElementType) ?? 'button';
|
|
102
|
-
let props;
|
|
103
|
-
|
|
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;
|
|
117
|
-
}
|
|
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
|
-
};
|
|
47
|
+
const contentClassNames = clsx('wds-Button-content', {
|
|
48
|
+
[`wds-Button-content--loading`]: loading,
|
|
49
|
+
});
|
|
132
50
|
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{...props}
|
|
139
|
-
aria-live={loading ? 'polite' : 'off'}
|
|
140
|
-
aria-busy={loading}
|
|
141
|
-
aria-label={loading ? intl.formatMessage(messages.loadingAriaLabel) : rest['aria-label']}
|
|
51
|
+
const content = (
|
|
52
|
+
<Body
|
|
53
|
+
as="span"
|
|
54
|
+
type={size === 'sm' ? Typography.BODY_DEFAULT_BOLD : Typography.BODY_LARGE_BOLD}
|
|
55
|
+
className={contentClassNames}
|
|
142
56
|
>
|
|
143
|
-
{children}
|
|
144
57
|
{loading && (
|
|
145
58
|
<ProcessIndicator
|
|
146
|
-
size={
|
|
147
|
-
className="
|
|
148
|
-
data-testid="
|
|
59
|
+
size={size === 'sm' ? 'xxs' : 'xs'}
|
|
60
|
+
className="wds-Button-loader"
|
|
61
|
+
data-testid="button-loader-indicator"
|
|
149
62
|
/>
|
|
150
63
|
)}
|
|
151
|
-
|
|
64
|
+
<span className="wds-Button-label" aria-hidden={loading}>
|
|
65
|
+
{size === 'lg' ? (
|
|
66
|
+
<span className="wds-Button-labelText">{children}</span>
|
|
67
|
+
) : (
|
|
68
|
+
<>
|
|
69
|
+
{size === 'md' && addonStart?.type === 'avatar' && addonStart.value && (
|
|
70
|
+
<span className="wds-Button-avatars">
|
|
71
|
+
<AvatarLayout orientation="horizontal" avatars={addonStart?.value} size={24} />
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{addonStart?.type === 'icon' && addonStart.value && (
|
|
76
|
+
<span className={`wds-Button-icon wds-Button-icon--${size} wds-Button-icon--start`}>
|
|
77
|
+
{addonStart.value}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
<span className="wds-Button-labelText">{children}</span>
|
|
82
|
+
|
|
83
|
+
{addonEnd?.type === 'icon' && addonEnd?.value && (
|
|
84
|
+
<span className={`wds-Button-icon wds-Button-icon--${size} wds-Button-icon--end`}>
|
|
85
|
+
{addonEnd.value}
|
|
86
|
+
</span>
|
|
87
|
+
)}
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
</span>
|
|
91
|
+
</Body>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if ((href && as !== 'button') || as === 'a') {
|
|
95
|
+
return (
|
|
96
|
+
<PrimitiveAnchor
|
|
97
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
98
|
+
{...(props as any)}
|
|
99
|
+
href={href}
|
|
100
|
+
className={classNames}
|
|
101
|
+
disabled={disabled || loading}
|
|
102
|
+
aria-busy={loading || undefined}
|
|
103
|
+
type={undefined}
|
|
104
|
+
>
|
|
105
|
+
{content}
|
|
106
|
+
</PrimitiveAnchor>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<PrimitiveButton
|
|
112
|
+
ref={ref as React.Ref<HTMLButtonElement>}
|
|
113
|
+
{...(props as any)}
|
|
114
|
+
className={classNames}
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
loading={loading}
|
|
117
|
+
type={type}
|
|
118
|
+
href={undefined}
|
|
119
|
+
target={undefined}
|
|
120
|
+
>
|
|
121
|
+
{content}
|
|
122
|
+
</PrimitiveButton>
|
|
152
123
|
);
|
|
153
124
|
},
|
|
154
125
|
);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import type { AvatarLayoutProps } from '../avatarLayout';
|
|
3
|
+
|
|
4
|
+
export type ButtonSentiment = 'default' | 'negative';
|
|
5
|
+
export type ButtonPriority = 'primary' | 'secondary' | 'secondary-neutral' | 'tertiary';
|
|
6
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
7
|
+
export type ButtonReferenceType = HTMLButtonElement | HTMLAnchorElement;
|
|
8
|
+
type ButtonAddonIcon = {
|
|
9
|
+
type: 'icon';
|
|
10
|
+
value: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
type ButtonAddonAvatar = {
|
|
13
|
+
type: 'avatar';
|
|
14
|
+
value: AvatarLayoutProps['avatars'];
|
|
15
|
+
};
|
|
16
|
+
type ButtonAddonStart = ButtonAddonIcon | ButtonAddonAvatar;
|
|
17
|
+
type ButtonAddonEnd = ButtonAddonIcon;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Common properties for the Button component.
|
|
21
|
+
*/
|
|
22
|
+
export interface CommonProps {
|
|
23
|
+
/**
|
|
24
|
+
* If set, toggles the new Button API
|
|
25
|
+
* @default false
|
|
26
|
+
* */
|
|
27
|
+
v2: true;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The HTML element to render. Takes precedence over `href` prop.
|
|
31
|
+
* @default undefined
|
|
32
|
+
**/
|
|
33
|
+
as?: 'button' | 'a';
|
|
34
|
+
|
|
35
|
+
/** @default false **/
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
|
|
38
|
+
/** If set, the component will render as an HTML anchor. */
|
|
39
|
+
href?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The `target` attribute for HTML anchor.
|
|
43
|
+
* If set to `_blank`, `rel="noopener noreferrer"` is automatically added to the rendered node.
|
|
44
|
+
*/
|
|
45
|
+
target?: string;
|
|
46
|
+
|
|
47
|
+
/** @default false */
|
|
48
|
+
loading?: boolean;
|
|
49
|
+
|
|
50
|
+
/** Makes the button take up the full width of its container */
|
|
51
|
+
block?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sets the size of the button
|
|
55
|
+
* @default lg
|
|
56
|
+
* */
|
|
57
|
+
size?: ButtonSize;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sets a visual hierarchy amongst the buttons displayed on the screen
|
|
61
|
+
* @default 'primary'
|
|
62
|
+
*/
|
|
63
|
+
priority?: ButtonPriority;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Emphasises the nature of the button's action
|
|
67
|
+
* @default default
|
|
68
|
+
*/
|
|
69
|
+
sentiment?: ButtonSentiment;
|
|
70
|
+
|
|
71
|
+
/** Accessory to be displayed before the label. Not supported by the `lg` size. */
|
|
72
|
+
addonStart?: ButtonAddonStart;
|
|
73
|
+
|
|
74
|
+
/** Accessory to be displayed after the label. Not supported by the `lg` size. */
|
|
75
|
+
addonEnd?: ButtonAddonEnd;
|
|
76
|
+
|
|
77
|
+
className?: string;
|
|
78
|
+
children?: ReactNode;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ButtonElementProps = Omit<
|
|
82
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
83
|
+
'disabled' | 'className'
|
|
84
|
+
> &
|
|
85
|
+
CommonProps;
|
|
86
|
+
export type AnchorElementProps = Omit<
|
|
87
|
+
AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
88
|
+
'type' | 'disabled' | 'href' | 'className'
|
|
89
|
+
> &
|
|
90
|
+
CommonProps;
|
|
91
|
+
|
|
92
|
+
export type ButtonProps = ButtonElementProps | AnchorElementProps;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.wds-Button {
|
|
2
|
+
--Button-background: var(--color-interactive-accent);
|
|
3
|
+
--Button-background-hover: var(--color-interactive-accent-hover);
|
|
4
|
+
--Button-background-active: var(--color-interactive-accent-active);
|
|
5
|
+
--Button-color: var(--color-interactive-control);
|
|
6
|
+
--Button-border-radius: var(--radius-full);
|
|
7
|
+
--Button-label-gap: var(--size-4);
|
|
8
|
+
--Button-large-padding: var(--size-12) var(--size-16);
|
|
9
|
+
--Button-medium-padding: var(--size-8) var(--size-12);
|
|
10
|
+
--Button-small-padding: var(--size-5) var(--size-12);
|
|
11
|
+
--Button-avatar-border-color: var(--color-border-neutral);
|
|
12
|
+
--Button-transition-duration: 150ms;
|
|
13
|
+
--Button-transition-easing: ease-in-out;
|
|
14
|
+
--Button-secondary-background: var(--color-interactive-neutral);
|
|
15
|
+
--Button-secondary-background-hover: var(--color-interactive-neutral-hover);
|
|
16
|
+
--Button-secondary-background-active: var(--color-interactive-neutral-active);
|
|
17
|
+
--Button-secondary-color: var(--color-interactive-primary);
|
|
18
|
+
--Button-secondary-neutral-background: var(--color-background-neutral);
|
|
19
|
+
--Button-secondary-neutral-background-hover: var(--color-background-neutral-hover);
|
|
20
|
+
--Button-secondary-neutral-background-active: var(--color-background-neutral-active);
|
|
21
|
+
--Button-secondary-neutral-color: var(--color-content-primary);
|
|
22
|
+
--Button-tertiary-background: transparent;
|
|
23
|
+
--Button-tertiary-background-hover: var(--color-background-screen-hover);
|
|
24
|
+
--Button-tertiary-background-active: var(--color-background-screen-active);
|
|
25
|
+
--Button-tertiary-color: var(--color-interactive-primary);
|
|
26
|
+
--Button-primary-negative-background: var(--color-sentiment-negative-primary);
|
|
27
|
+
--Button-primary-negative-background-hover: var(--color-sentiment-negative-primary-hover);
|
|
28
|
+
--Button-primary-negative-background-active: var(--color-sentiment-negative-primary-active);
|
|
29
|
+
--Button-primary-negative-color: var(--color-contrast-overlay);
|
|
30
|
+
--Button-secondary-negative-background: var(--color-sentiment-negative-secondary);
|
|
31
|
+
--Button-secondary-negative-background-hover: var(--color-sentiment-negative-secondary-hover);
|
|
32
|
+
--Button-secondary-negative-background-active: var(--color-sentiment-negative-secondary-active);
|
|
33
|
+
--Button-secondary-negative-color: var(--color-sentiment-negative-primary);
|
|
34
|
+
}
|
|
35
|
+
.np-theme-personal--bright-green .wds-Button {
|
|
36
|
+
--Button-primary-negative-color: var(--color-white);
|
|
37
|
+
--Button-secondary-color: var(--color-interactive-control);
|
|
38
|
+
--Button-secondary-negative-color: var(--color-white);
|
|
39
|
+
}
|
|
40
|
+
.np-theme-personal--forest-green .wds-Button {
|
|
41
|
+
--Button-secondary-background: var(--color-interactive-neutral);
|
|
42
|
+
--Button-secondary-negative-background: var(--color-sentiment-negative-primary);
|
|
43
|
+
--Button-secondary-negative-background-hover: var(--color-sentiment-negative-primary-hover);
|
|
44
|
+
--Button-secondary-negative-background-active: var(--color-sentiment-negative-primary-active);
|
|
45
|
+
--Button-secondary-negative-color: var(--color-contrast-overlay);
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
.wds-Button {
|
|
2
|
+
--Button-background: var(--color-interactive-accent);
|
|
3
|
+
--Button-background-hover: var(--color-interactive-accent-hover);
|
|
4
|
+
--Button-background-active: var(--color-interactive-accent-active);
|
|
5
|
+
--Button-color: var(--color-interactive-control);
|
|
6
|
+
--Button-border-radius: var(--radius-full);
|
|
7
|
+
--Button-label-gap: var(--size-4);
|
|
8
|
+
|
|
9
|
+
--Button-large-padding: var(--size-12) var(--size-16);
|
|
10
|
+
--Button-medium-padding: var(--size-8) var(--size-12);
|
|
11
|
+
--Button-small-padding: var(--size-5) var(--size-12);
|
|
12
|
+
|
|
13
|
+
--Button-avatar-border-color: var(--color-border-neutral);
|
|
14
|
+
--Button-transition-duration: 150ms;
|
|
15
|
+
--Button-transition-easing: ease-in-out;
|
|
16
|
+
|
|
17
|
+
--Button-secondary-background: var(--color-interactive-neutral);
|
|
18
|
+
--Button-secondary-background-hover: var(--color-interactive-neutral-hover);
|
|
19
|
+
--Button-secondary-background-active: var(--color-interactive-neutral-active);
|
|
20
|
+
--Button-secondary-color: var(--color-interactive-primary);
|
|
21
|
+
|
|
22
|
+
--Button-secondary-neutral-background: var(--color-background-neutral);
|
|
23
|
+
--Button-secondary-neutral-background-hover: var(--color-background-neutral-hover);
|
|
24
|
+
--Button-secondary-neutral-background-active: var(--color-background-neutral-active);
|
|
25
|
+
--Button-secondary-neutral-color: var(--color-content-primary);
|
|
26
|
+
|
|
27
|
+
--Button-tertiary-background: transparent;
|
|
28
|
+
--Button-tertiary-background-hover: var(--color-background-screen-hover);
|
|
29
|
+
--Button-tertiary-background-active: var(--color-background-screen-active);
|
|
30
|
+
--Button-tertiary-color: var(--color-interactive-primary);
|
|
31
|
+
|
|
32
|
+
--Button-primary-negative-background: var(--color-sentiment-negative-primary);
|
|
33
|
+
--Button-primary-negative-background-hover: var(--color-sentiment-negative-primary-hover);
|
|
34
|
+
--Button-primary-negative-background-active: var(--color-sentiment-negative-primary-active);
|
|
35
|
+
--Button-primary-negative-color: var(--color-contrast-overlay);
|
|
36
|
+
|
|
37
|
+
--Button-secondary-negative-background: var(--color-sentiment-negative-secondary);
|
|
38
|
+
--Button-secondary-negative-background-hover: var(--color-sentiment-negative-secondary-hover);
|
|
39
|
+
--Button-secondary-negative-background-active: var(--color-sentiment-negative-secondary-active);
|
|
40
|
+
--Button-secondary-negative-color: var(--color-sentiment-negative-primary);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
.np-theme-personal--bright-green & {
|
|
44
|
+
--Button-primary-negative-color: var(--color-white);
|
|
45
|
+
--Button-secondary-color: var(--color-interactive-control);
|
|
46
|
+
--Button-secondary-negative-color: var(--color-white);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
.np-theme-personal--forest-green & {
|
|
51
|
+
--Button-secondary-background: var(--color-interactive-neutral);
|
|
52
|
+
--Button-secondary-negative-background: var(--color-sentiment-negative-primary);
|
|
53
|
+
--Button-secondary-negative-background-hover: var(--color-sentiment-negative-primary-hover);
|
|
54
|
+
--Button-secondary-negative-background-active: var(--color-sentiment-negative-primary-active);
|
|
55
|
+
--Button-secondary-negative-color: var(--color-contrast-overlay);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.np-btn {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
.np-btn.np-btn-block > span.btn-loader {
|
|
5
|
+
position: absolute;
|
|
6
|
+
left: 24px;
|
|
7
|
+
left: var(--size-24);
|
|
8
|
+
}
|
|
9
|
+
[dir="rtl"] .np-btn.np-btn-block > span.btn-loader {
|
|
10
|
+
right: 24px;
|
|
11
|
+
right: var(--size-24);
|
|
12
|
+
left: auto;
|
|
13
|
+
left: initial;
|
|
14
|
+
}
|
|
15
|
+
.np-btn.np-btn-xs > span.btn-loader {
|
|
16
|
+
background-size: 16px 16px;
|
|
17
|
+
}
|
|
18
|
+
.np-btn.np-btn-xs.btn-block > span.btn-loader {
|
|
19
|
+
top: 0;
|
|
20
|
+
}
|
|
21
|
+
.np-btn.disabled[class] {
|
|
22
|
+
pointer-events: auto;
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@import (reference) "../../node_modules/@transferwise/neptune-css/src/less/mixins/_logical-properties.less";
|
|
2
|
+
|
|
3
|
+
.np-btn {
|
|
4
|
+
position: relative;
|
|
5
|
+
|
|
6
|
+
&.np-btn-block > span.btn-loader {
|
|
7
|
+
position: absolute;
|
|
8
|
+
.left(var(--size-24));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
&.np-btn-xs > span.btn-loader {
|
|
12
|
+
background-size: 16px 16px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&.np-btn-xs.btn-block > span.btn-loader {
|
|
16
|
+
top: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// we do not want pointer events disabled as that
|
|
20
|
+
// prevents the custom cursor to be shown
|
|
21
|
+
&.disabled[class] {
|
|
22
|
+
pointer-events: auto;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { render, screen, userEvent } from '../test-utils';
|
|
4
|
+
|
|
5
|
+
import LegacyButton from './LegacyButton';
|
|
6
|
+
import messages from '../i18n/commonMessages/Button.messages';
|
|
7
|
+
import { ButtonReferenceType } from './Button.types';
|
|
8
|
+
|
|
9
|
+
describe('LegacyButton', () => {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
const originalWarn = console.warn;
|
|
12
|
+
let mockedWarn: typeof originalWarn;
|
|
13
|
+
|
|
14
|
+
const props = {
|
|
15
|
+
onClick: jest.fn(),
|
|
16
|
+
onFocus: jest.fn(),
|
|
17
|
+
onBlur: jest.fn(),
|
|
18
|
+
children: 'Send money',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
mockedWarn = jest.fn().mockImplementation((args) => {
|
|
23
|
+
if (typeof args !== 'string' || !args.startsWith('Button has deprecated the')) {
|
|
24
|
+
originalWarn(args);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.warn = mockedWarn;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
beforeEach(jest.clearAllMocks);
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.warn = originalWarn;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('by default', () => {
|
|
39
|
+
it('renders the text', () => {
|
|
40
|
+
render(<LegacyButton {...props} />);
|
|
41
|
+
expect(screen.getByText(props.children)).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('set `ref` to be true on Button', () => {
|
|
45
|
+
const reference = createRef<ButtonReferenceType>();
|
|
46
|
+
|
|
47
|
+
expect(reference.current).toBeFalsy();
|
|
48
|
+
render(<LegacyButton ref={reference}>Click me!</LegacyButton>);
|
|
49
|
+
expect(reference.current).toBeTruthy();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('is not disabled', () => {
|
|
53
|
+
render(<LegacyButton {...props} />);
|
|
54
|
+
expect(screen.getByRole('button')).toBeEnabled();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('button attributes', () => {
|
|
59
|
+
it('sets the htmlType if set', () => {
|
|
60
|
+
render(<LegacyButton {...props} htmlType="submit" />);
|
|
61
|
+
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('passes through custom classes if set', () => {
|
|
65
|
+
render(<LegacyButton {...props} className="donkeysarethebest" />);
|
|
66
|
+
expect(screen.getByRole('button')).toHaveClass('donkeysarethebest');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('passes through aria-label if set', () => {
|
|
70
|
+
render(<LegacyButton {...props} aria-label="unique label" />);
|
|
71
|
+
const loadingButton = screen.getByLabelText('unique label');
|
|
72
|
+
expect(loadingButton).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('onClick', () => {
|
|
77
|
+
it('calls onClick when clicked', async () => {
|
|
78
|
+
render(<LegacyButton {...props} />);
|
|
79
|
+
await userEvent.click(screen.getByRole('button'));
|
|
80
|
+
expect(props.onClick).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('does not call onClick when clicked if disabled', async () => {
|
|
84
|
+
render(<LegacyButton {...props} disabled />);
|
|
85
|
+
await userEvent.click(screen.getByRole('button'));
|
|
86
|
+
expect(props.onClick).toHaveBeenCalledTimes(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not call onClick when clicked if loading', async () => {
|
|
90
|
+
render(<LegacyButton {...props} loading />);
|
|
91
|
+
await userEvent.click(screen.getByRole('button'));
|
|
92
|
+
expect(props.onClick).toHaveBeenCalledTimes(0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('onFocus and onBlur', () => {
|
|
97
|
+
it('calls both handlers by default', async () => {
|
|
98
|
+
render(<LegacyButton {...props} />);
|
|
99
|
+
await userEvent.tab();
|
|
100
|
+
expect(props.onFocus).toHaveBeenCalledTimes(1);
|
|
101
|
+
await userEvent.tab();
|
|
102
|
+
expect(props.onFocus).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('does not call either handler if disabled', async () => {
|
|
106
|
+
render(<LegacyButton {...props} disabled />);
|
|
107
|
+
await userEvent.tab();
|
|
108
|
+
expect(props.onFocus).not.toHaveBeenCalled();
|
|
109
|
+
await userEvent.tab();
|
|
110
|
+
expect(props.onFocus).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('calls both handlers if loading', async () => {
|
|
114
|
+
render(<LegacyButton {...props} loading />);
|
|
115
|
+
await userEvent.tab();
|
|
116
|
+
expect(props.onFocus).toHaveBeenCalledTimes(1);
|
|
117
|
+
await userEvent.tab();
|
|
118
|
+
expect(props.onFocus).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('other states', () => {
|
|
123
|
+
it('renders as loading if `loading` is true', () => {
|
|
124
|
+
render(<LegacyButton {...props} loading />);
|
|
125
|
+
const button = screen.queryByRole('button', {
|
|
126
|
+
name: messages.loadingAriaLabel.defaultMessage,
|
|
127
|
+
});
|
|
128
|
+
expect(button).toBeInTheDocument();
|
|
129
|
+
expect(button).toBeEnabled();
|
|
130
|
+
expect(button).toHaveClass('btn-loading');
|
|
131
|
+
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
132
|
+
expect(button).toHaveAttribute('aria-busy', 'true');
|
|
133
|
+
expect(button).toHaveAttribute('aria-live', 'polite');
|
|
134
|
+
expect(screen.getByTestId('ButtonProgressIndicator')).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('disables the button', () => {
|
|
138
|
+
render(<LegacyButton {...props} disabled />);
|
|
139
|
+
const button = screen.queryByRole('button');
|
|
140
|
+
expect(button).toBeDisabled();
|
|
141
|
+
expect(button).toHaveClass('disabled');
|
|
142
|
+
expect(button).toHaveAttribute('aria-disabled', 'false');
|
|
143
|
+
expect(button).toHaveAttribute('aria-busy', 'false');
|
|
144
|
+
expect(button).toHaveAttribute('aria-live', 'off');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|