@transferwise/components 46.80.0 → 46.82.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/avatar/Avatar.js +3 -0
- package/build/avatar/Avatar.js.map +1 -1
- package/build/avatar/Avatar.mjs +3 -0
- package/build/avatar/Avatar.mjs.map +1 -1
- package/build/avatarView/AvatarView.js +175 -0
- package/build/avatarView/AvatarView.js.map +1 -0
- package/build/avatarView/AvatarView.mjs +173 -0
- package/build/avatarView/AvatarView.mjs.map +1 -0
- package/build/avatarView/NotificationDot.js +59 -0
- package/build/avatarView/NotificationDot.js.map +1 -0
- package/build/avatarView/NotificationDot.mjs +57 -0
- package/build/avatarView/NotificationDot.mjs.map +1 -0
- package/build/avatarWrapper/AvatarWrapper.js +10 -4
- package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
- package/build/avatarWrapper/AvatarWrapper.mjs +10 -4
- package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
- package/build/badge/Badge.js +16 -4
- package/build/badge/Badge.js.map +1 -1
- package/build/badge/Badge.mjs +15 -3
- package/build/badge/Badge.mjs.map +1 -1
- package/build/badge/BadgeAssets.js +60 -0
- package/build/badge/BadgeAssets.js.map +1 -0
- package/build/badge/BadgeAssets.mjs +58 -0
- package/build/badge/BadgeAssets.mjs.map +1 -0
- package/build/common/circle/Circle.js +19 -1
- package/build/common/circle/Circle.js.map +1 -1
- package/build/common/circle/Circle.mjs +19 -1
- package/build/common/circle/Circle.mjs.map +1 -1
- package/build/i18n/en.json +5 -0
- package/build/i18n/en.json.js +5 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +5 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/i18n/zh-HK.json +5 -0
- package/build/i18n/zh-HK.json.js +5 -0
- package/build/i18n/zh-HK.json.js.map +1 -1
- package/build/i18n/zh-HK.json.mjs +5 -0
- package/build/i18n/zh-HK.json.mjs.map +1 -1
- package/build/index.js +18 -13
- package/build/index.js.map +1 -1
- package/build/index.mjs +10 -7
- package/build/index.mjs.map +1 -1
- package/build/main.css +348 -5
- package/build/money/Money.js +5 -2
- package/build/money/Money.js.map +1 -1
- package/build/money/Money.mjs +5 -2
- package/build/money/Money.mjs.map +1 -1
- package/build/styles/avatarView/AvatarView.css +36 -0
- package/build/styles/avatarView/NotificationDot.css +20 -0
- package/build/styles/badge/Badge.css +6 -5
- package/build/styles/common/circle/Circle.css +32 -0
- package/build/styles/main.css +348 -5
- package/build/styles/table/Table.css +274 -0
- package/build/types/avatar/Avatar.d.ts +3 -0
- package/build/types/avatar/Avatar.d.ts.map +1 -1
- package/build/types/avatarView/AvatarView.d.ts +26 -0
- package/build/types/avatarView/AvatarView.d.ts.map +1 -0
- package/build/types/avatarView/NotificationDot.d.ts +8 -0
- package/build/types/avatarView/NotificationDot.d.ts.map +1 -0
- package/build/types/avatarView/index.d.ts +3 -0
- package/build/types/avatarView/index.d.ts.map +1 -0
- package/build/types/avatarWrapper/AvatarWrapper.d.ts +3 -0
- package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
- package/build/types/badge/Badge.d.ts +9 -4
- package/build/types/badge/Badge.d.ts.map +1 -1
- package/build/types/badge/BadgeAssets.d.ts +14 -0
- package/build/types/badge/BadgeAssets.d.ts.map +1 -0
- package/build/types/badge/index.d.ts +2 -0
- package/build/types/badge/index.d.ts.map +1 -1
- package/build/types/common/circle/Circle.d.ts +2 -0
- package/build/types/common/circle/Circle.d.ts.map +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/money/Money.d.ts +2 -1
- package/build/types/money/Money.d.ts.map +1 -1
- package/build/types/table/Table.d.ts +23 -0
- package/build/types/table/Table.d.ts.map +1 -0
- package/build/types/table/Table.messages.d.ts +24 -0
- package/build/types/table/Table.messages.d.ts.map +1 -0
- package/build/types/table/TableCell.d.ts +40 -0
- package/build/types/table/TableCell.d.ts.map +1 -0
- package/build/types/table/TableHeader.d.ts +13 -0
- package/build/types/table/TableHeader.d.ts.map +1 -0
- package/build/types/table/TableRow.d.ts +17 -0
- package/build/types/table/TableRow.d.ts.map +1 -0
- package/build/types/table/TableStatusText.d.ts +10 -0
- package/build/types/table/TableStatusText.d.ts.map +1 -0
- package/build/types/table/index.d.ts +6 -0
- package/build/types/table/index.d.ts.map +1 -0
- package/build/types/test-utils/index.d.ts +10 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/avatar/Avatar.tsx +3 -0
- package/src/avatarView/AvatarView.css +36 -0
- package/src/avatarView/AvatarView.less +27 -0
- package/src/avatarView/AvatarView.story.tsx +467 -0
- package/src/avatarView/AvatarView.tsx +171 -0
- package/src/avatarView/NotificationDot.css +20 -0
- package/src/avatarView/NotificationDot.less +24 -0
- package/src/avatarView/NotificationDot.tsx +35 -0
- package/src/avatarView/index.ts +2 -0
- package/src/avatarWrapper/AvatarWrapper.story.tsx +19 -0
- package/src/avatarWrapper/AvatarWrapper.tsx +3 -0
- package/src/badge/Badge.css +6 -5
- package/src/badge/Badge.less +4 -3
- package/src/badge/Badge.tsx +20 -6
- package/src/badge/BadgeAssets.tsx +61 -0
- package/src/badge/index.ts +3 -0
- package/src/circularButton/CircularButton.spec.tsx +0 -36
- package/src/common/circle/Circle.css +32 -0
- package/src/common/circle/Circle.less +35 -0
- package/src/common/circle/Circle.tsx +24 -1
- package/src/flowNavigation/FlowNavigation.story.tsx +19 -52
- package/src/i18n/en.json +5 -0
- package/src/i18n/zh-HK.json +5 -0
- package/src/index.ts +3 -0
- package/src/listItem/ListItem.story.tsx +5 -47
- package/src/main.css +348 -5
- package/src/main.less +2 -0
- package/src/money/Money.tsx +9 -2
- package/src/overlayHeader/OverlayHeader.story.tsx +6 -14
- package/src/table/Table.css +274 -0
- package/src/table/Table.less +334 -0
- package/src/table/Table.messages.ts +24 -0
- package/src/table/Table.spec.tsx +82 -0
- package/src/table/Table.story.tsx +356 -0
- package/src/table/Table.tsx +167 -0
- package/src/table/TableCell.spec.tsx +298 -0
- package/src/table/TableCell.tsx +149 -0
- package/src/table/TableHeader.spec.tsx +50 -0
- package/src/table/TableHeader.tsx +74 -0
- package/src/table/TableRow.spec.tsx +112 -0
- package/src/table/TableRow.tsx +70 -0
- package/src/table/TableStatusText.spec.tsx +53 -0
- package/src/table/TableStatusText.tsx +40 -0
- package/src/table/index.ts +11 -0
- package/src/circularButton/__snapshots__/CircularButton.spec.tsx.snap +0 -381
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import Badge, { BadgeAssets, BadgeProps, BadgeAssetsProps } from '../badge';
|
|
2
|
+
import NotificationDot from './NotificationDot';
|
|
3
|
+
import Circle from '../common/circle';
|
|
4
|
+
import Image from '../image';
|
|
5
|
+
import { HTMLAttributes, PropsWithChildren, useState } from 'react';
|
|
6
|
+
import { clsx } from 'clsx';
|
|
7
|
+
import { getInitials, ProfileType, ProfileTypeBusiness, ProfileTypePersonal } from '../common';
|
|
8
|
+
import {
|
|
9
|
+
Briefcase as BusinessProfileIcon,
|
|
10
|
+
Profile as PersonalProfileIcon,
|
|
11
|
+
} from '@transferwise/icons';
|
|
12
|
+
|
|
13
|
+
export type AvatarViewBadgeProps = BadgeAssetsProps &
|
|
14
|
+
Pick<BadgeProps, 'aria-label'> & {
|
|
15
|
+
/**
|
|
16
|
+
* Custom badge content
|
|
17
|
+
*/
|
|
18
|
+
asset?: React.ReactNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type Props = {
|
|
22
|
+
imgSrc?: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Entity name (person (profile or recipient) or business name), it will be boilded down to 1 or 2 chars (initials)
|
|
25
|
+
*/
|
|
26
|
+
profileName?: string | null;
|
|
27
|
+
profileType?: ProfileTypeBusiness | ProfileTypePersonal;
|
|
28
|
+
size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
|
|
29
|
+
notification?: boolean;
|
|
30
|
+
badge?: AvatarViewBadgeProps;
|
|
31
|
+
interactive?: boolean;
|
|
32
|
+
selected?: boolean;
|
|
33
|
+
style?: Pick<React.CSSProperties, 'border' | 'backgroundColor' | 'color'>;
|
|
34
|
+
} & Pick<
|
|
35
|
+
HTMLAttributes<HTMLDivElement>,
|
|
36
|
+
'className' | 'children' | 'role' | 'aria-label' | 'aria-labelledby' | 'aria-hidden'
|
|
37
|
+
>;
|
|
38
|
+
|
|
39
|
+
function AvatarView({
|
|
40
|
+
children = undefined,
|
|
41
|
+
size = 48,
|
|
42
|
+
selected,
|
|
43
|
+
notification,
|
|
44
|
+
badge,
|
|
45
|
+
interactive = false,
|
|
46
|
+
className,
|
|
47
|
+
style,
|
|
48
|
+
imgSrc,
|
|
49
|
+
profileType,
|
|
50
|
+
profileName,
|
|
51
|
+
...restProps
|
|
52
|
+
}: Props) {
|
|
53
|
+
return (
|
|
54
|
+
<Circle
|
|
55
|
+
size={size}
|
|
56
|
+
fixedSize
|
|
57
|
+
className={clsx(
|
|
58
|
+
'np-avatar-view',
|
|
59
|
+
interactive ? 'np-avatar-view-interactive' : 'np-avatar-view-non-interactive',
|
|
60
|
+
{ 'np-avatar-view-selected': selected },
|
|
61
|
+
className,
|
|
62
|
+
)}
|
|
63
|
+
{...restProps}
|
|
64
|
+
>
|
|
65
|
+
<Badges avatar={{ size, notification, selected }} {...badge}>
|
|
66
|
+
<Circle
|
|
67
|
+
size={size}
|
|
68
|
+
fixedSize
|
|
69
|
+
className={clsx('np-avatar-view-content')}
|
|
70
|
+
enableBorder={!interactive || selected}
|
|
71
|
+
style={{
|
|
72
|
+
...(selected && { '--circle-border-color': 'var(--color-interactive-primary)' }),
|
|
73
|
+
...style,
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<AvatarViewContent {...{ imgSrc, profileType, profileName }}>
|
|
77
|
+
{children}
|
|
78
|
+
</AvatarViewContent>
|
|
79
|
+
</Circle>
|
|
80
|
+
</Badges>
|
|
81
|
+
</Circle>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Size of badge depends on size of avatar */
|
|
86
|
+
const MAP_BADGE_ASSET_SIZE = {
|
|
87
|
+
16: 16,
|
|
88
|
+
24: 16,
|
|
89
|
+
32: 16,
|
|
90
|
+
40: 16,
|
|
91
|
+
48: 16,
|
|
92
|
+
56: 24,
|
|
93
|
+
72: 24,
|
|
94
|
+
} satisfies Record<number, BadgeAssetsProps['size']>;
|
|
95
|
+
|
|
96
|
+
/** Certain sizes of AvatarView has a custom offset for badge */
|
|
97
|
+
const MAP_BADGE_POSITION = {
|
|
98
|
+
24: -6,
|
|
99
|
+
32: -4,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type BadgesProps = AvatarViewBadgeProps &
|
|
103
|
+
PropsWithChildren<{
|
|
104
|
+
avatar: Pick<Props, 'selected' | 'size' | 'notification'>;
|
|
105
|
+
}>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Adds build-in badges to AvatarView
|
|
109
|
+
*/
|
|
110
|
+
function Badges({
|
|
111
|
+
children,
|
|
112
|
+
avatar,
|
|
113
|
+
'aria-label': ariaLabel,
|
|
114
|
+
asset: customBadge,
|
|
115
|
+
...badgeAssets
|
|
116
|
+
}: BadgesProps) {
|
|
117
|
+
const { size = 48, selected, notification } = avatar;
|
|
118
|
+
const anyBadge = Object.values({ customBadge, ...badgeAssets }).filter(Boolean).length > 0;
|
|
119
|
+
if ((anyBadge || selected) && size > 16) {
|
|
120
|
+
const badgeSize: BadgeAssetsProps['size'] = MAP_BADGE_ASSET_SIZE[size];
|
|
121
|
+
return (
|
|
122
|
+
<Badge
|
|
123
|
+
aria-label={ariaLabel}
|
|
124
|
+
size={badgeSize}
|
|
125
|
+
badge={
|
|
126
|
+
customBadge ? (
|
|
127
|
+
<Circle fixedSize size={badgeSize}>
|
|
128
|
+
{customBadge}
|
|
129
|
+
</Circle>
|
|
130
|
+
) : (
|
|
131
|
+
<BadgeAssets {...(selected ? { status: 'positive' } : badgeAssets)} size={badgeSize} />
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
style={{
|
|
135
|
+
// @ts-expect-error CSS custom props allowed
|
|
136
|
+
'--badge-content-position': `${MAP_BADGE_POSITION[size] ?? 0}px`,
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{children}
|
|
140
|
+
</Badge>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (notification) {
|
|
144
|
+
return <NotificationDot avatarSize={size}>{children}</NotificationDot>;
|
|
145
|
+
}
|
|
146
|
+
return children;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function AvatarViewContent({
|
|
150
|
+
children,
|
|
151
|
+
imgSrc,
|
|
152
|
+
profileName: name,
|
|
153
|
+
profileType: type,
|
|
154
|
+
}: PropsWithChildren<Pick<Props, 'imgSrc' | 'profileName' | 'profileType'>>) {
|
|
155
|
+
const [tryLoadImage, setTryLoadImage] = useState(true);
|
|
156
|
+
if (children === undefined) {
|
|
157
|
+
if (imgSrc && tryLoadImage) {
|
|
158
|
+
return <Image src={imgSrc} alt="" onError={() => setTryLoadImage(false)} />;
|
|
159
|
+
}
|
|
160
|
+
if (type) {
|
|
161
|
+
return type === ProfileType.BUSINESS ? <BusinessProfileIcon /> : <PersonalProfileIcon />;
|
|
162
|
+
}
|
|
163
|
+
if (name) {
|
|
164
|
+
return getInitials(name);
|
|
165
|
+
}
|
|
166
|
+
return <PersonalProfileIcon />;
|
|
167
|
+
}
|
|
168
|
+
return children;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default AvatarView;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.np-notification-dot {
|
|
2
|
+
--np-notification-dot-size: 14px;
|
|
3
|
+
position: relative;
|
|
4
|
+
display: inline-block;
|
|
5
|
+
}
|
|
6
|
+
.np-notification-dot-mask {
|
|
7
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-notification-dot-size) / 2)) left calc(100% - (var(--np-notification-dot-size) / 2)), transparent 0, transparent calc(var(--np-notification-dot-size) / 2 + var(--np-notification-dot-offset)), black 0);
|
|
8
|
+
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-notification-dot-size) / 2)) left calc(100% - (var(--np-notification-dot-size) / 2)), transparent 0, transparent calc(var(--np-notification-dot-size) / 2 + var(--np-notification-dot-offset)), black 0);
|
|
9
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-notification-dot-size) / 2)) left calc(100% - calc(var(--np-notification-dot-size) / 2)), transparent 0, transparent calc(var(--np-notification-dot-size) / 2 + var(--np-notification-dot-offset)), black 0);
|
|
10
|
+
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-notification-dot-size) / 2)) left calc(100% - calc(var(--np-notification-dot-size) / 2)), transparent 0, transparent calc(var(--np-notification-dot-size) / 2 + var(--np-notification-dot-offset)), black 0);
|
|
11
|
+
}
|
|
12
|
+
.np-notification-dot-badge {
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: var(--np-notification-dot-size);
|
|
15
|
+
height: var(--np-notification-dot-size);
|
|
16
|
+
background-color: var(--color-sentiment-negative);
|
|
17
|
+
border-radius: 9999px;
|
|
18
|
+
border-radius: var(--radius-full);
|
|
19
|
+
right: 0;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.np-notification-dot {
|
|
2
|
+
--np-notification-dot-size: 14px;
|
|
3
|
+
position: relative;
|
|
4
|
+
display: inline-block;
|
|
5
|
+
|
|
6
|
+
&-mask {
|
|
7
|
+
mask-image: radial-gradient(
|
|
8
|
+
circle at bottom calc(100% - calc(var(--np-notification-dot-size) / 2))
|
|
9
|
+
left calc(100% - calc(var(--np-notification-dot-size) / 2)),
|
|
10
|
+
transparent 0,
|
|
11
|
+
transparent calc(var(--np-notification-dot-size) / 2 + var(--np-notification-dot-offset)),
|
|
12
|
+
black 0
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&-badge {
|
|
17
|
+
position: absolute;
|
|
18
|
+
width: var(--np-notification-dot-size);
|
|
19
|
+
height: var(--np-notification-dot-size);
|
|
20
|
+
background-color: var(--color-sentiment-negative);
|
|
21
|
+
border-radius: var(--radius-full);
|
|
22
|
+
right: 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { Props as AvatarViewProps } from './AvatarView';
|
|
3
|
+
|
|
4
|
+
type Props = Pick<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
5
|
+
avatarSize?: AvatarViewProps['size'];
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Depending on avatar size, notifcation dot size and offset are different
|
|
10
|
+
*/
|
|
11
|
+
const MAP_STYLE_CONFIG = {
|
|
12
|
+
16: { size: 6, offset: 1 },
|
|
13
|
+
24: { size: 8, offset: 2 },
|
|
14
|
+
32: { size: 10, offset: 2 },
|
|
15
|
+
40: { size: 10, offset: 2 },
|
|
16
|
+
48: { size: 14, offset: 2 },
|
|
17
|
+
56: { size: 16, offset: 3 },
|
|
18
|
+
72: { size: 20, offset: 3 },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function NotificationDot({ children, avatarSize = 48 }: Props) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className="np-notification-dot"
|
|
25
|
+
style={{
|
|
26
|
+
// @ts-expect-error CSS custom props allowed
|
|
27
|
+
'--np-notification-dot-size': `${MAP_STYLE_CONFIG[avatarSize].size}px`,
|
|
28
|
+
'--np-notification-dot-offset': `${MAP_STYLE_CONFIG[avatarSize].offset}px`,
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
<div className="np-notification-dot-badge" />
|
|
32
|
+
<div className="np-notification-dot-mask">{children}</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -99,3 +99,22 @@ export const All: Story = {
|
|
|
99
99
|
);
|
|
100
100
|
},
|
|
101
101
|
};
|
|
102
|
+
|
|
103
|
+
export const ProfileBrokenImageFallback: Story = {
|
|
104
|
+
parameters: {
|
|
105
|
+
chromatic: {
|
|
106
|
+
delay: 5000,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
render: () => {
|
|
110
|
+
const assetUrl = 'https://test.com/img-wrong-url.test';
|
|
111
|
+
return (
|
|
112
|
+
<>
|
|
113
|
+
<AvatarWrapper />
|
|
114
|
+
<AvatarWrapper url={assetUrl} />
|
|
115
|
+
<AvatarWrapper url={assetUrl} profileType={ProfileType.BUSINESS} />
|
|
116
|
+
<AvatarWrapper url={assetUrl} profileType={ProfileType.BUSINESS} name="Test Name" />
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
};
|
package/src/badge/Badge.css
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
--badge-mask: 2px;
|
|
6
6
|
--badge-mask-offset: calc(var(--badge-size) / 2);
|
|
7
7
|
--badge-border-color: rgba(255, 255, 255, 0.08);
|
|
8
|
+
--badge-content-position: 0px;
|
|
8
9
|
}
|
|
9
10
|
.tw-badge.tw-badge-lg {
|
|
10
11
|
--badge-size: 24px;
|
|
@@ -14,8 +15,8 @@
|
|
|
14
15
|
--badge-border-color: rgba(0, 0, 0, 0.08);
|
|
15
16
|
}
|
|
16
17
|
.tw-badge > .tw-badge__children {
|
|
17
|
-
-webkit-mask-image: radial-gradient(circle at top calc(100% - var(--badge-mask-offset)) left calc(100% - var(--badge-mask-offset)), transparent 0, transparent calc(var(--badge-size) / 2 + var(--badge-mask)), black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px));
|
|
18
|
-
mask-image: radial-gradient(circle at top calc(100% - var(--badge-mask-offset)) left calc(100% - var(--badge-mask-offset)), transparent 0, transparent calc(var(--badge-size) / 2 + var(--badge-mask)), black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px));
|
|
18
|
+
-webkit-mask-image: radial-gradient(circle at top calc(100% - var(--badge-mask-offset) - var(--badge-content-position)) left calc(100% - var(--badge-mask-offset) - var(--badge-content-position)), transparent 0, transparent calc(var(--badge-size) / 2 + var(--badge-mask)), black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px));
|
|
19
|
+
mask-image: radial-gradient(circle at top calc(100% - var(--badge-mask-offset) - var(--badge-content-position)) left calc(100% - var(--badge-mask-offset) - var(--badge-content-position)), transparent 0, transparent calc(var(--badge-size) / 2 + var(--badge-mask)), black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px));
|
|
19
20
|
}
|
|
20
21
|
[dir="rtl"] .tw-badge > .tw-badge__children {
|
|
21
22
|
-webkit-mask-image: radial-gradient(circle at top calc(100% - var(--badge-mask-offset)) right calc(100% - var(--badge-mask-offset)), transparent 0, transparent calc(var(--badge-size) / 2 + var(--badge-mask)), black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px));
|
|
@@ -25,8 +26,8 @@
|
|
|
25
26
|
position: absolute;
|
|
26
27
|
width: var(--badge-size);
|
|
27
28
|
height: var(--badge-size);
|
|
28
|
-
bottom:
|
|
29
|
-
right:
|
|
29
|
+
bottom: var(--badge-content-position);
|
|
30
|
+
right: var(--badge-content-position);
|
|
30
31
|
box-sizing: border-box;
|
|
31
32
|
border-radius: 50%;
|
|
32
33
|
text-align: center;
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
user-select: none;
|
|
40
41
|
}
|
|
41
42
|
[dir="rtl"] .tw-badge > .tw-badge__content {
|
|
42
|
-
left:
|
|
43
|
+
left: var(--badge-content-position);
|
|
43
44
|
right: auto;
|
|
44
45
|
right: initial;
|
|
45
46
|
}
|
package/src/badge/Badge.less
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
--badge-mask: @badge-mask-sm;
|
|
18
18
|
--badge-mask-offset: calc(var(--badge-size) / 2);
|
|
19
19
|
--badge-border-color: @badge-border-light;
|
|
20
|
+
--badge-content-position: 0px;
|
|
20
21
|
|
|
21
22
|
&.tw-badge-lg {
|
|
22
23
|
--badge-size: @badge-size-lg;
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
|
|
34
35
|
mask-image:
|
|
35
36
|
radial-gradient(
|
|
36
|
-
circle at top calc(100% - var(--badge-mask-offset)) left calc(100% - var(--badge-mask-offset)),
|
|
37
|
+
circle at top calc(100% - var(--badge-mask-offset) - var(--badge-content-position)) left calc(100% - var(--badge-mask-offset) - var(--badge-content-position)),
|
|
37
38
|
transparent 0,
|
|
38
39
|
transparent calc(var(--badge-size) / 2 + var(--badge-mask)),
|
|
39
40
|
black calc(var(--badge-size) / 2 + var(--badge-mask) + 0.5px)
|
|
@@ -54,8 +55,8 @@
|
|
|
54
55
|
position: absolute;
|
|
55
56
|
width: var(--badge-size);
|
|
56
57
|
height: var(--badge-size);
|
|
57
|
-
bottom:
|
|
58
|
-
.right(
|
|
58
|
+
bottom: var(--badge-content-position);
|
|
59
|
+
.right(var(--badge-content-position));
|
|
59
60
|
|
|
60
61
|
box-sizing: border-box;
|
|
61
62
|
border-radius: 50%;
|
package/src/badge/Badge.tsx
CHANGED
|
@@ -11,11 +11,12 @@ import {
|
|
|
11
11
|
ThemeLight,
|
|
12
12
|
CommonProps,
|
|
13
13
|
} from '../common';
|
|
14
|
+
import { BadgeAssetsProps } from '.';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* @deprecated Use `
|
|
17
|
+
* @deprecated Use `16` or `24` instead.
|
|
17
18
|
*/
|
|
18
|
-
type DeprecatedSizes = SizeMedium;
|
|
19
|
+
type DeprecatedSizes = SizeSmall | SizeMedium | SizeLarge;
|
|
19
20
|
|
|
20
21
|
export type BadgeProps = {
|
|
21
22
|
badge: ReactNode;
|
|
@@ -23,11 +24,24 @@ export type BadgeProps = {
|
|
|
23
24
|
/**
|
|
24
25
|
* `md` is deprecated, it will fallback to `sm` instead.
|
|
25
26
|
*/
|
|
26
|
-
size?:
|
|
27
|
+
size?: DeprecatedSizes | BadgeAssetsProps['size'];
|
|
27
28
|
border?: ThemeDark | ThemeLight;
|
|
28
29
|
'aria-label'?: string;
|
|
30
|
+
style?: React.CSSProperties;
|
|
29
31
|
} & CommonProps;
|
|
30
32
|
|
|
33
|
+
const mapLegacySize = {
|
|
34
|
+
16: Size.SMALL,
|
|
35
|
+
24: Size.LARGE,
|
|
36
|
+
// medium is no longer exists, so we map it to small
|
|
37
|
+
[String(Size.MEDIUM)]: Size.SMALL,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Note: Badge component is not deprecated, we want stop it's direct usage on consumer side.
|
|
41
|
+
// Deprecation notice will hint consumers to migrate. Eventually the component will become internal.
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Use `<AvatarView badge={..} />` instead.
|
|
44
|
+
*/
|
|
31
45
|
const Badge = ({
|
|
32
46
|
badge,
|
|
33
47
|
className = undefined,
|
|
@@ -35,9 +49,9 @@ const Badge = ({
|
|
|
35
49
|
border = Theme.LIGHT,
|
|
36
50
|
'aria-label': ariaLabel,
|
|
37
51
|
children,
|
|
52
|
+
style,
|
|
38
53
|
}: BadgeProps) => {
|
|
39
|
-
|
|
40
|
-
const size = sizeProp === Size.MEDIUM ? Size.SMALL : sizeProp;
|
|
54
|
+
const size = mapLegacySize[sizeProp] ?? sizeProp;
|
|
41
55
|
const classes: string = clsx(
|
|
42
56
|
'tw-badge',
|
|
43
57
|
{
|
|
@@ -48,7 +62,7 @@ const Badge = ({
|
|
|
48
62
|
);
|
|
49
63
|
|
|
50
64
|
return (
|
|
51
|
-
<div aria-label={ariaLabel} className={classes}>
|
|
65
|
+
<div aria-label={ariaLabel} className={classes} style={style}>
|
|
52
66
|
<div className="tw-badge__children">{children}</div>
|
|
53
67
|
<div className="tw-badge__content">{badge}</div>
|
|
54
68
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import StatusIcon, { StatusIconProps } from '../statusIcon';
|
|
2
|
+
import { Flag } from '@wise/art';
|
|
3
|
+
import Circle from '../common/circle';
|
|
4
|
+
import Image from '../image';
|
|
5
|
+
import { Plus } from '@transferwise/icons';
|
|
6
|
+
|
|
7
|
+
export type Props = {
|
|
8
|
+
status?: StatusIconProps['sentiment'];
|
|
9
|
+
flagCode?: string;
|
|
10
|
+
imgSrc?: string;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
type?: 'action' | 'reference';
|
|
13
|
+
size?: 16 | 24;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Common pre-built badge variants.
|
|
18
|
+
*/
|
|
19
|
+
export default function BadgeAssets({
|
|
20
|
+
status,
|
|
21
|
+
flagCode,
|
|
22
|
+
imgSrc,
|
|
23
|
+
icon = null,
|
|
24
|
+
type = 'action',
|
|
25
|
+
size,
|
|
26
|
+
}: Props) {
|
|
27
|
+
if (status) {
|
|
28
|
+
return <StatusIcon sentiment={status} size={size} />;
|
|
29
|
+
}
|
|
30
|
+
if (flagCode) {
|
|
31
|
+
return (
|
|
32
|
+
<Circle size={size} fixedSize enableBorder>
|
|
33
|
+
<Flag code={flagCode} intrinsicSize={size} />
|
|
34
|
+
</Circle>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (imgSrc) {
|
|
38
|
+
return (
|
|
39
|
+
<Circle size={size} fixedSize enableBorder>
|
|
40
|
+
<Image src={imgSrc} alt="" />
|
|
41
|
+
</Circle>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (['action', 'reference'].includes(type)) {
|
|
45
|
+
return (
|
|
46
|
+
<Circle
|
|
47
|
+
size={size}
|
|
48
|
+
fixedSize
|
|
49
|
+
style={{
|
|
50
|
+
backgroundColor:
|
|
51
|
+
type === 'action'
|
|
52
|
+
? 'var(--color-interactive-accent)'
|
|
53
|
+
: 'var(--color-background-neutral)',
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{icon ?? <Plus />}
|
|
57
|
+
</Circle>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
package/src/badge/index.ts
CHANGED
|
@@ -34,10 +34,6 @@ describe('CircularButton', () => {
|
|
|
34
34
|
it('is not disabled', () => {
|
|
35
35
|
expect(screen.getByRole('button')).toBeEnabled();
|
|
36
36
|
});
|
|
37
|
-
|
|
38
|
-
it('renders a button of type accent and priority primary', () => {
|
|
39
|
-
expect(render(<CircularButton {...props} />).container).toMatchSnapshot();
|
|
40
|
-
});
|
|
41
37
|
});
|
|
42
38
|
|
|
43
39
|
describe('button attributes', () => {
|
|
@@ -69,36 +65,4 @@ describe('CircularButton', () => {
|
|
|
69
65
|
expect(onClick).toHaveBeenCalledTimes(0);
|
|
70
66
|
});
|
|
71
67
|
});
|
|
72
|
-
|
|
73
|
-
describe('types', () => {
|
|
74
|
-
it('renders accent buttons', () => {
|
|
75
|
-
expect(render(<CircularButton {...props} type={ACCENT} />).container).toMatchSnapshot();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('renders positive buttons', () => {
|
|
79
|
-
expect(render(<CircularButton {...props} type={POSITIVE} />).container).toMatchSnapshot();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('renders negative buttons', () => {
|
|
83
|
-
expect(render(<CircularButton {...props} type={NEGATIVE} />).container).toMatchSnapshot();
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('priorities', () => {
|
|
88
|
-
it('renders primary buttons', () => {
|
|
89
|
-
[ACCENT, POSITIVE, NEGATIVE].forEach((type) =>
|
|
90
|
-
expect(
|
|
91
|
-
render(<CircularButton {...props} priority={PRIMARY} type={type} />).container,
|
|
92
|
-
).toMatchSnapshot(),
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('renders secondary buttons', () => {
|
|
97
|
-
[ACCENT, POSITIVE, NEGATIVE].forEach((type) =>
|
|
98
|
-
expect(
|
|
99
|
-
render(<CircularButton {...props} priority={SECONDARY} type={type} />).container,
|
|
100
|
-
).toMatchSnapshot(),
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
68
|
});
|
|
@@ -4,8 +4,40 @@
|
|
|
4
4
|
width: var(--circle-size);
|
|
5
5
|
height: var(--circle-size);
|
|
6
6
|
flex-shrink: 0;
|
|
7
|
+
--circle-border-color: var(--color-border-neutral);
|
|
8
|
+
--circle-border-width: 1px;
|
|
9
|
+
font-size: var(--circle-font-size);
|
|
10
|
+
font-weight: 600;
|
|
11
|
+
font-weight: var(--font-weight-semi-bold);
|
|
12
|
+
line-height: 1;
|
|
13
|
+
}
|
|
14
|
+
.np-circle .np-display {
|
|
15
|
+
font-size: var(--circle-font-size);
|
|
7
16
|
}
|
|
8
17
|
.np-circle .tw-icon > svg {
|
|
9
18
|
height: var(--circle-icon-size);
|
|
10
19
|
width: var(--circle-icon-size);
|
|
11
20
|
}
|
|
21
|
+
.np-circle img,
|
|
22
|
+
.np-circle .wds-flag {
|
|
23
|
+
border-radius: 9999px;
|
|
24
|
+
border-radius: var(--radius-full);
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
-o-object-fit: cover;
|
|
28
|
+
object-fit: cover;
|
|
29
|
+
}
|
|
30
|
+
.np-circle-border {
|
|
31
|
+
position: relative;
|
|
32
|
+
}
|
|
33
|
+
.np-circle-border::after {
|
|
34
|
+
content: "";
|
|
35
|
+
position: absolute;
|
|
36
|
+
top: 0;
|
|
37
|
+
left: 0;
|
|
38
|
+
width: 100%;
|
|
39
|
+
height: 100%;
|
|
40
|
+
border-radius: 9999px;
|
|
41
|
+
border-radius: var(--radius-full);
|
|
42
|
+
box-shadow: inset 0 0 0 var(--circle-border-width) var(--circle-border-color);
|
|
43
|
+
}
|
|
@@ -4,9 +4,44 @@
|
|
|
4
4
|
height: var(--circle-size);
|
|
5
5
|
flex-shrink: 0;
|
|
6
6
|
|
|
7
|
+
--circle-border-color: var(--color-border-neutral);
|
|
8
|
+
--circle-border-width: 1px;
|
|
9
|
+
|
|
10
|
+
// circle like components have custom typography styles for for default (Inter) font
|
|
11
|
+
font-size: var(--circle-font-size);
|
|
12
|
+
font-weight: var(--font-weight-semi-bold);
|
|
13
|
+
line-height: 1;
|
|
14
|
+
|
|
15
|
+
// circle like components have custom typography styles for Wise Sand font
|
|
16
|
+
.np-display {
|
|
17
|
+
font-size: var(--circle-font-size);
|
|
18
|
+
}
|
|
19
|
+
|
|
7
20
|
// circle like components has custom icon sizes
|
|
8
21
|
.tw-icon > svg {
|
|
9
22
|
height: var(--circle-icon-size);
|
|
10
23
|
width: var(--circle-icon-size);
|
|
11
24
|
}
|
|
25
|
+
|
|
26
|
+
img,
|
|
27
|
+
.wds-flag {
|
|
28
|
+
border-radius: var(--radius-full);
|
|
29
|
+
width: 100%;
|
|
30
|
+
height: 100%;
|
|
31
|
+
object-fit: cover;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&-border {
|
|
35
|
+
position: relative;
|
|
36
|
+
&::after {
|
|
37
|
+
content: "";
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 0;
|
|
40
|
+
left: 0;
|
|
41
|
+
width: 100%;
|
|
42
|
+
height: 100%;
|
|
43
|
+
border-radius: var(--radius-full);
|
|
44
|
+
box-shadow: inset 0 0 0 var(--circle-border-width) var(--circle-border-color);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
12
47
|
}
|
|
@@ -19,6 +19,7 @@ export type Props = {
|
|
|
19
19
|
* as those can be dynamic a at certain viewport sizes
|
|
20
20
|
*/
|
|
21
21
|
fixedSize?: boolean;
|
|
22
|
+
enableBorder?: boolean;
|
|
22
23
|
} & HTMLAttributes<HTMLDivElement>;
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -34,12 +35,28 @@ const MAP_ICON_SIZE = {
|
|
|
34
35
|
72: 36,
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* circle like components have custom typography styles for for default (Inter) font
|
|
40
|
+
*
|
|
41
|
+
* circle size : font size (px)
|
|
42
|
+
*/
|
|
43
|
+
const MAP_FONT_SIZE = {
|
|
44
|
+
16: 8,
|
|
45
|
+
24: 12,
|
|
46
|
+
32: 14,
|
|
47
|
+
40: 18,
|
|
48
|
+
48: 22,
|
|
49
|
+
56: 26,
|
|
50
|
+
72: 30,
|
|
51
|
+
};
|
|
52
|
+
|
|
37
53
|
const Circle = forwardRef(function Circle(
|
|
38
54
|
{
|
|
39
55
|
as: Element = 'div',
|
|
40
56
|
children,
|
|
41
57
|
size = 48,
|
|
42
58
|
fixedSize = false,
|
|
59
|
+
enableBorder = false,
|
|
43
60
|
className,
|
|
44
61
|
style,
|
|
45
62
|
...props
|
|
@@ -57,9 +74,15 @@ const Circle = forwardRef(function Circle(
|
|
|
57
74
|
isTinyViewport && !fixedSize
|
|
58
75
|
? `${MAP_ICON_SIZE[size] / 2}px`
|
|
59
76
|
: `${MAP_ICON_SIZE[size]}px`,
|
|
77
|
+
'--circle-font-size': `${MAP_FONT_SIZE[size]}px`,
|
|
60
78
|
...style,
|
|
61
79
|
}}
|
|
62
|
-
className={clsx(
|
|
80
|
+
className={clsx(
|
|
81
|
+
'np-circle',
|
|
82
|
+
{ 'np-circle-border': enableBorder },
|
|
83
|
+
'd-flex align-items-center justify-content-center',
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
63
86
|
>
|
|
64
87
|
{children}
|
|
65
88
|
</Element>
|