@transferwise/components 0.0.0-experimental-8d0974a → 0.0.0-experimental-53b8447

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 (207) hide show
  1. package/build/avatar/Avatar.js +3 -0
  2. package/build/avatar/Avatar.js.map +1 -1
  3. package/build/avatar/Avatar.mjs +3 -0
  4. package/build/avatar/Avatar.mjs.map +1 -1
  5. package/build/avatarView/AvatarView.js +187 -0
  6. package/build/avatarView/AvatarView.js.map +1 -0
  7. package/build/avatarView/AvatarView.mjs +185 -0
  8. package/build/avatarView/AvatarView.mjs.map +1 -0
  9. package/build/avatarView/NotificationDot.js +56 -0
  10. package/build/avatarView/NotificationDot.js.map +1 -0
  11. package/build/avatarView/NotificationDot.mjs +54 -0
  12. package/build/avatarView/NotificationDot.mjs.map +1 -0
  13. package/build/avatarWrapper/AvatarWrapper.js +6 -1
  14. package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
  15. package/build/avatarWrapper/AvatarWrapper.mjs +6 -1
  16. package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
  17. package/build/badge/Badge.js +8 -1
  18. package/build/badge/Badge.js.map +1 -1
  19. package/build/badge/Badge.mjs +8 -1
  20. package/build/badge/Badge.mjs.map +1 -1
  21. package/build/badge/BadgeAssets.js +63 -0
  22. package/build/badge/BadgeAssets.js.map +1 -0
  23. package/build/badge/BadgeAssets.mjs +61 -0
  24. package/build/badge/BadgeAssets.mjs.map +1 -0
  25. package/build/common/circle/Circle.js +29 -3
  26. package/build/common/circle/Circle.js.map +1 -1
  27. package/build/common/circle/Circle.mjs +29 -3
  28. package/build/common/circle/Circle.mjs.map +1 -1
  29. package/build/dateLookup/DateLookup.js +6 -6
  30. package/build/dateLookup/DateLookup.js.map +1 -1
  31. package/build/dateLookup/DateLookup.mjs +6 -6
  32. package/build/dateLookup/DateLookup.mjs.map +1 -1
  33. package/build/dateLookup/dayCalendar/table/DayCalendarTable.js +1 -4
  34. package/build/dateLookup/dayCalendar/table/DayCalendarTable.js.map +1 -1
  35. package/build/dateLookup/dayCalendar/table/DayCalendarTable.mjs +1 -4
  36. package/build/dateLookup/dayCalendar/table/DayCalendarTable.mjs.map +1 -1
  37. package/build/i18n/de.json +5 -0
  38. package/build/i18n/de.json.js +5 -0
  39. package/build/i18n/de.json.js.map +1 -1
  40. package/build/i18n/de.json.mjs +5 -0
  41. package/build/i18n/de.json.mjs.map +1 -1
  42. package/build/i18n/es.json +5 -0
  43. package/build/i18n/es.json.js +5 -0
  44. package/build/i18n/es.json.js.map +1 -1
  45. package/build/i18n/es.json.mjs +5 -0
  46. package/build/i18n/es.json.mjs.map +1 -1
  47. package/build/i18n/fr.json +5 -0
  48. package/build/i18n/fr.json.js +5 -0
  49. package/build/i18n/fr.json.js.map +1 -1
  50. package/build/i18n/fr.json.mjs +5 -0
  51. package/build/i18n/fr.json.mjs.map +1 -1
  52. package/build/i18n/hu.json +5 -0
  53. package/build/i18n/hu.json.js +5 -0
  54. package/build/i18n/hu.json.js.map +1 -1
  55. package/build/i18n/hu.json.mjs +5 -0
  56. package/build/i18n/hu.json.mjs.map +1 -1
  57. package/build/i18n/id.json +5 -0
  58. package/build/i18n/id.json.js +5 -0
  59. package/build/i18n/id.json.js.map +1 -1
  60. package/build/i18n/id.json.mjs +5 -0
  61. package/build/i18n/id.json.mjs.map +1 -1
  62. package/build/i18n/it.json +5 -0
  63. package/build/i18n/it.json.js +5 -0
  64. package/build/i18n/it.json.js.map +1 -1
  65. package/build/i18n/it.json.mjs +5 -0
  66. package/build/i18n/it.json.mjs.map +1 -1
  67. package/build/i18n/ja.json +5 -0
  68. package/build/i18n/ja.json.js +5 -0
  69. package/build/i18n/ja.json.js.map +1 -1
  70. package/build/i18n/ja.json.mjs +5 -0
  71. package/build/i18n/ja.json.mjs.map +1 -1
  72. package/build/i18n/pl.json +5 -0
  73. package/build/i18n/pl.json.js +5 -0
  74. package/build/i18n/pl.json.js.map +1 -1
  75. package/build/i18n/pl.json.mjs +5 -0
  76. package/build/i18n/pl.json.mjs.map +1 -1
  77. package/build/i18n/pt.json +5 -0
  78. package/build/i18n/pt.json.js +5 -0
  79. package/build/i18n/pt.json.js.map +1 -1
  80. package/build/i18n/pt.json.mjs +5 -0
  81. package/build/i18n/pt.json.mjs.map +1 -1
  82. package/build/i18n/ro.json +5 -0
  83. package/build/i18n/ro.json.js +5 -0
  84. package/build/i18n/ro.json.js.map +1 -1
  85. package/build/i18n/ro.json.mjs +5 -0
  86. package/build/i18n/ro.json.mjs.map +1 -1
  87. package/build/i18n/ru.json +5 -0
  88. package/build/i18n/ru.json.js +5 -0
  89. package/build/i18n/ru.json.js.map +1 -1
  90. package/build/i18n/ru.json.mjs +5 -0
  91. package/build/i18n/ru.json.mjs.map +1 -1
  92. package/build/i18n/tr.json +5 -0
  93. package/build/i18n/tr.json.js +5 -0
  94. package/build/i18n/tr.json.js.map +1 -1
  95. package/build/i18n/tr.json.mjs +5 -0
  96. package/build/i18n/tr.json.mjs.map +1 -1
  97. package/build/index.js +15 -4
  98. package/build/index.js.map +1 -1
  99. package/build/index.mjs +10 -2
  100. package/build/index.mjs.map +1 -1
  101. package/build/main.css +98 -25
  102. package/build/statusIcon/StatusIcon.js +2 -2
  103. package/build/statusIcon/StatusIcon.js.map +1 -1
  104. package/build/statusIcon/StatusIcon.mjs +2 -2
  105. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  106. package/build/styles/avatarGroup/AvatarGroup.css +28 -0
  107. package/build/styles/avatarView/AvatarView.css +28 -0
  108. package/build/styles/avatarView/NotificationDot.css +20 -0
  109. package/build/styles/badge/Badge.css +6 -5
  110. package/build/styles/common/circle/Circle.css +36 -0
  111. package/build/styles/main.css +98 -25
  112. package/build/styles/statusIcon/StatusIcon.css +0 -20
  113. package/build/types/avatar/Avatar.d.ts +3 -0
  114. package/build/types/avatar/Avatar.d.ts.map +1 -1
  115. package/build/types/avatarGroup/AvatarGroup.d.ts +18 -0
  116. package/build/types/avatarGroup/AvatarGroup.d.ts.map +1 -0
  117. package/build/types/avatarGroup/index.d.ts +3 -0
  118. package/build/types/avatarGroup/index.d.ts.map +1 -0
  119. package/build/types/avatarView/AvatarView.d.ts +25 -0
  120. package/build/types/avatarView/AvatarView.d.ts.map +1 -0
  121. package/build/types/avatarView/NotificationDot.d.ts +8 -0
  122. package/build/types/avatarView/NotificationDot.d.ts.map +1 -0
  123. package/build/types/avatarView/index.d.ts +3 -0
  124. package/build/types/avatarView/index.d.ts.map +1 -0
  125. package/build/types/avatarWrapper/AvatarWrapper.d.ts +3 -0
  126. package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
  127. package/build/types/badge/Badge.d.ts +5 -1
  128. package/build/types/badge/Badge.d.ts.map +1 -1
  129. package/build/types/badge/BadgeAssets.d.ts +15 -0
  130. package/build/types/badge/BadgeAssets.d.ts.map +1 -0
  131. package/build/types/badge/index.d.ts +2 -0
  132. package/build/types/badge/index.d.ts.map +1 -1
  133. package/build/types/common/circle/Circle.d.ts +3 -1
  134. package/build/types/common/circle/Circle.d.ts.map +1 -1
  135. package/build/types/common/dateUtils/index.d.ts +0 -1
  136. package/build/types/common/dateUtils/index.d.ts.map +1 -1
  137. package/build/types/dateLookup/DateLookup.d.ts +1 -1
  138. package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
  139. package/build/types/dateLookup/dayCalendar/table/DayCalendarTable.d.ts +1 -1
  140. package/build/types/dateLookup/dayCalendar/table/DayCalendarTable.d.ts.map +1 -1
  141. package/build/types/index.d.ts +3 -1
  142. package/build/types/index.d.ts.map +1 -1
  143. package/build/types/statusIcon/StatusIcon.d.ts +6 -2
  144. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  145. package/package.json +3 -3
  146. package/src/avatar/Avatar.tsx +3 -0
  147. package/src/avatarGroup/AvatarGroup.css +28 -0
  148. package/src/avatarGroup/AvatarGroup.less +32 -0
  149. package/src/avatarGroup/AvatarGroup.story.tsx +195 -0
  150. package/src/avatarGroup/AvatarGroup.tsx +80 -0
  151. package/src/avatarGroup/index.ts +2 -0
  152. package/src/avatarView/AvatarView.css +28 -0
  153. package/src/avatarView/AvatarView.less +16 -0
  154. package/src/avatarView/AvatarView.story.tsx +573 -0
  155. package/src/avatarView/AvatarView.tsx +184 -0
  156. package/src/avatarView/NotificationDot.css +20 -0
  157. package/src/avatarView/NotificationDot.less +24 -0
  158. package/src/avatarView/NotificationDot.tsx +32 -0
  159. package/src/avatarView/index.ts +2 -0
  160. package/src/avatarWrapper/AvatarWrapper.story.tsx +6 -0
  161. package/src/avatarWrapper/AvatarWrapper.tsx +3 -0
  162. package/src/badge/Badge.css +6 -5
  163. package/src/badge/Badge.less +4 -3
  164. package/src/badge/Badge.tsx +8 -1
  165. package/src/badge/BadgeAssets.tsx +65 -0
  166. package/src/badge/index.ts +3 -0
  167. package/src/common/circle/Circle.css +36 -0
  168. package/src/common/circle/Circle.less +42 -1
  169. package/src/common/circle/Circle.tsx +41 -3
  170. package/src/common/dateUtils/index.ts +0 -1
  171. package/src/dateLookup/DateLookup.tests.story.tsx +5 -40
  172. package/src/dateLookup/DateLookup.tsx +11 -9
  173. package/src/dateLookup/dayCalendar/table/DayCalendarTable.tsx +2 -5
  174. package/src/decision/Decision.story.tsx +10 -46
  175. package/src/flowNavigation/FlowNavigation.story.js +10 -39
  176. package/src/i18n/de.json +5 -0
  177. package/src/i18n/es.json +5 -0
  178. package/src/i18n/fr.json +5 -0
  179. package/src/i18n/hu.json +5 -0
  180. package/src/i18n/id.json +5 -0
  181. package/src/i18n/it.json +5 -0
  182. package/src/i18n/ja.json +5 -0
  183. package/src/i18n/pl.json +5 -0
  184. package/src/i18n/pt.json +5 -0
  185. package/src/i18n/ro.json +5 -0
  186. package/src/i18n/ru.json +5 -0
  187. package/src/i18n/tr.json +5 -0
  188. package/src/index.ts +4 -0
  189. package/src/listItem/ListItem.story.tsx +6 -43
  190. package/src/main.css +98 -25
  191. package/src/main.less +2 -0
  192. package/src/navigationOption/NavigationOption.story.js +14 -65
  193. package/src/overlayHeader/OverlayHeader.story.tsx +5 -10
  194. package/src/radio/Radio.story.tsx +5 -5
  195. package/src/radioGroup/RadioGroup.story.tsx +3 -3
  196. package/src/selectOption/SelectOption.story.tsx +31 -30
  197. package/src/statusIcon/StatusIcon.css +0 -20
  198. package/src/statusIcon/StatusIcon.less +0 -17
  199. package/src/statusIcon/StatusIcon.tsx +14 -4
  200. package/src/tile/Tile.story.tsx +2 -6
  201. package/build/common/dateUtils/getDateView/getDateView.js +0 -10
  202. package/build/common/dateUtils/getDateView/getDateView.js.map +0 -1
  203. package/build/common/dateUtils/getDateView/getDateView.mjs +0 -8
  204. package/build/common/dateUtils/getDateView/getDateView.mjs.map +0 -1
  205. package/build/types/common/dateUtils/getDateView/getDateView.d.ts +0 -2
  206. package/build/types/common/dateUtils/getDateView/getDateView.d.ts.map +0 -1
  207. package/src/common/dateUtils/getDateView/getDateView.ts +0 -5
@@ -0,0 +1,184 @@
1
+ import Badge, { BadgeAssets, BadgeProps, BadgeAssetsProps } from '../badge';
2
+ import NotificationDot from './NotificationDot';
3
+ import Circle, { CircleProps } from '../common/circle';
4
+ import Image from '../image';
5
+ import { HTMLAttributes, PropsWithChildren, useState } from 'react';
6
+ import { ActionOptions as ActionProps } from '../common/action/Action';
7
+ import { clsx } from 'clsx';
8
+ import { getInitials, Size } from '../common';
9
+ import {
10
+ Briefcase as BusinessProfileIcon,
11
+ Profile as PersonalProfileIcon,
12
+ } from '@transferwise/icons';
13
+
14
+ type InteractiveProps = Omit<ActionProps, 'text'> &
15
+ Pick<HTMLAttributes<HTMLDivElement>, 'className' | 'aria-labelledby'>;
16
+
17
+ export type Props = {
18
+ imgSrc?: string | null;
19
+ /**
20
+ * Entity name (personal initials or business name), it will be boilded down to 1 or 2 chars (initials)
21
+ */
22
+ name?: string | null;
23
+ type?: 'personal' | 'business';
24
+ size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
25
+ /** adds prebuilt notification dot via Badge component */
26
+ notification?: boolean;
27
+ // badge?: React.ReactNode;
28
+ badge?: BadgeAssetsProps;
29
+ /** changes background color to neutral and wraps component with button */
30
+ action?: InteractiveProps;
31
+ /** changes background color to neutral and adds predefined Badge */
32
+ selected?: boolean;
33
+ /** often use case to override `background-color` or `border` */
34
+ style?: Pick<React.CSSProperties, 'border' | 'backgroundColor' | 'color'>;
35
+ } & Pick<HTMLAttributes<HTMLDivElement>, 'className' | 'children'>;
36
+
37
+ function AvatarView({
38
+ children = undefined,
39
+ size = 48,
40
+ selected,
41
+ notification,
42
+ badge,
43
+ action,
44
+ className,
45
+ style,
46
+ imgSrc,
47
+ type,
48
+ name,
49
+ }: Props) {
50
+ const isInteractive = Boolean(action?.onClick ?? action?.href);
51
+ return (
52
+ <Interactive
53
+ {...action}
54
+ className={clsx('np-avatar-view', { 'np-avatar-view-selected': selected }, className)}
55
+ >
56
+ <Badges avatar={{ size, notification, selected }} {...badge}>
57
+ <Circle
58
+ size={size}
59
+ fixedSize
60
+ className={clsx('np-avatar-view-content')}
61
+ enableBorder={!isInteractive || selected}
62
+ style={{
63
+ ...(selected && { '--circle-border-color': 'var(--color-interactive-primary)' }),
64
+ backgroundColor: isInteractive ? 'var(--color-background-neutral)' : 'transparent',
65
+ ...style,
66
+ }}
67
+ >
68
+ {children === undefined ? <Profile {...{ imgSrc, type, name }} /> : children}
69
+ </Circle>
70
+ </Badges>
71
+ </Interactive>
72
+ );
73
+ }
74
+
75
+ function Interactive({
76
+ children,
77
+ onClick,
78
+ href,
79
+ target,
80
+ className,
81
+ 'aria-label': ariaLabel,
82
+ 'aria-labelledby': ariaLabelledby,
83
+ }: PropsWithChildren<InteractiveProps>) {
84
+ const elementName: React.ElementType | null = onClick ? 'button' : href ? 'a' : null;
85
+ if (elementName === null) {
86
+ // non interactive
87
+ return <div className={clsx(className, 'np-avatar-view-non-interactive')}>{children}</div>;
88
+ }
89
+ const a11yProps = { ariaLabel, ariaLabelledby };
90
+ if (elementName === 'button') {
91
+ return (
92
+ <button
93
+ type="button"
94
+ className={clsx(className, 'btn-unstyled')}
95
+ onClick={onClick}
96
+ {...a11yProps}
97
+ >
98
+ {children}
99
+ </button>
100
+ );
101
+ }
102
+ if (elementName === 'a') {
103
+ return (
104
+ // eslint-disable-next-line react/jsx-no-target-blank
105
+ <a
106
+ href={href}
107
+ target={target}
108
+ rel={target === '_blank' ? 'noreferrer' : undefined}
109
+ className={clsx(className, 'text-no-decoration')}
110
+ {...a11yProps}
111
+ >
112
+ {children}
113
+ </a>
114
+ );
115
+ }
116
+ }
117
+
118
+ /** Size of badge depends on size of avatar */
119
+ const MAP_BADGE_ASSET_SIZE = {
120
+ 16: 'sm',
121
+ 24: 'sm',
122
+ 32: 'sm',
123
+ 40: 'sm',
124
+ 48: 'sm',
125
+ 56: 'lg',
126
+ 72: 'lg',
127
+ };
128
+
129
+ /** Certain sizes of AvatarView has a custom offset of Badge */
130
+ const MAP_BADGE_POSITION = {
131
+ 24: -6,
132
+ 32: -4,
133
+ };
134
+
135
+ type BadgesProps = BadgeAssetsProps &
136
+ PropsWithChildren<{
137
+ avatar: Pick<Props, 'selected' | 'size' | 'notification'>;
138
+ }>;
139
+
140
+ /**
141
+ * Adds build-in badges to AvatarView
142
+ */
143
+ function Badges({ children, avatar, ...badge }: BadgesProps) {
144
+ const { size = 48, selected, notification } = avatar;
145
+ const anyCustomBadge = Object.keys(badge).length > 0;
146
+ if ((anyCustomBadge || selected) && size > 16) {
147
+ const badgeSize: BadgeProps['size'] = MAP_BADGE_ASSET_SIZE[size] ?? Size.SMALL;
148
+ return (
149
+ <Badge
150
+ size={badgeSize}
151
+ badge={<BadgeAssets {...(selected ? { status: 'positive' } : badge)} size={badgeSize} />}
152
+ style={{
153
+ // @ts-expect-error CSS custom props allowed
154
+ '--badge-content-position': `${MAP_BADGE_POSITION[size] ?? 0}px`,
155
+ }}
156
+ >
157
+ {children}
158
+ </Badge>
159
+ );
160
+ }
161
+ if (notification) {
162
+ return <NotificationDot avatarSize={size}>{children}</NotificationDot>;
163
+ }
164
+ return children;
165
+ }
166
+
167
+ /**
168
+ * Common logic for entities (personal & business profiles or recipients)
169
+ */
170
+ function Profile({ imgSrc, name, type }: Pick<Props, 'imgSrc' | 'name' | 'type'>) {
171
+ const [tryLoadImage, setTryLoadImage] = useState(true);
172
+ if (imgSrc && tryLoadImage) {
173
+ return <Image src={imgSrc} alt="" onError={() => setTryLoadImage(false)} />;
174
+ }
175
+ if (type) {
176
+ return type === 'business' ? <BusinessProfileIcon /> : <PersonalProfileIcon />;
177
+ }
178
+ if (name) {
179
+ return getInitials(name);
180
+ }
181
+ return <PersonalProfileIcon />;
182
+ }
183
+
184
+ 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,32 @@
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
+ const MAP_STYLE_CONFIG = {
9
+ 16: { size: 6, offset: 1 },
10
+ 24: { size: 8, offset: 2 },
11
+ 32: { size: 10, offset: 2 },
12
+ 40: { size: 10, offset: 2 },
13
+ 48: { size: 14, offset: 2 },
14
+ 56: { size: 16, offset: 3 },
15
+ 72: { size: 20, offset: 3 },
16
+ };
17
+
18
+ export default function NotificationDot({ children, avatarSize = 48 }: Props) {
19
+ return (
20
+ <div
21
+ className="np-notification-dot"
22
+ style={{
23
+ // @ts-expect-error CSS custom props allowed
24
+ '--np-notification-dot-size': `${MAP_STYLE_CONFIG[avatarSize].size}px`,
25
+ '--np-notification-dot-offset': `${MAP_STYLE_CONFIG[avatarSize].offset}px`,
26
+ }}
27
+ >
28
+ <div className="np-notification-dot-badge" />
29
+ <div className="np-notification-dot-mask">{children}</div>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,2 @@
1
+ export type { Props as AvatarViewProps } from './AvatarView';
2
+ export { default } from './AvatarView';
@@ -94,6 +94,12 @@ export const All: Story = {
94
94
  <div>
95
95
  <AvatarWrapper badgeStatusIcon={Sentiment.PENDING} avatarProps={avatarProps} />
96
96
  </div>
97
+ <div>
98
+ <AvatarWrapper
99
+ avatarProps={avatarProps}
100
+ badgeUrl="https://wise.com/web-art/assets/flags/jpy.svg"
101
+ />
102
+ </div>
97
103
  </div>
98
104
  </div>
99
105
  );
@@ -75,6 +75,9 @@ export type AvatarWrapperProps = {
75
75
  }
76
76
  );
77
77
 
78
+ /**
79
+ * @deprecated Use `AvatarView` component instead
80
+ */
78
81
  const AvatarWrapper = ({
79
82
  url,
80
83
  'aria-label': ariaLabel,
@@ -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));
@@ -23,8 +24,8 @@
23
24
  }
24
25
  .tw-badge > .tw-badge__content {
25
26
  position: absolute;
26
- bottom: 0;
27
- right: 0;
27
+ bottom: var(--badge-content-position);
28
+ right: var(--badge-content-position);
28
29
  box-sizing: border-box;
29
30
  text-align: center;
30
31
  overflow: hidden;
@@ -33,7 +34,7 @@
33
34
  user-select: none;
34
35
  }
35
36
  [dir="rtl"] .tw-badge > .tw-badge__content {
36
- left: 0;
37
+ left: var(--badge-content-position);
37
38
  right: auto;
38
39
  right: initial;
39
40
  }
@@ -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)
@@ -52,8 +53,8 @@
52
53
 
53
54
  & > &__content {
54
55
  position: absolute;
55
- bottom: 0;
56
- .right(0);
56
+ bottom: var(--badge-content-position);
57
+ .right(var(--badge-content-position));
57
58
 
58
59
  box-sizing: border-box;
59
60
  text-align: center;
@@ -27,6 +27,7 @@ export type BadgeProps = {
27
27
  size?: SizeSmall | DeprecatedSizes | SizeLarge;
28
28
  border?: ThemeDark | ThemeLight;
29
29
  'aria-label'?: string;
30
+ style?: React.CSSProperties;
30
31
  } & CommonProps;
31
32
 
32
33
  const mapLegacySize = {
@@ -36,6 +37,11 @@ const mapLegacySize = {
36
37
  [String(Size.LARGE)]: 24,
37
38
  } satisfies Record<string, CircleProps['size']>;
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
+ */
39
45
  const Badge = ({
40
46
  badge,
41
47
  className = undefined,
@@ -43,6 +49,7 @@ const Badge = ({
43
49
  border = Theme.LIGHT,
44
50
  'aria-label': ariaLabel,
45
51
  children,
52
+ style,
46
53
  }: BadgeProps) => {
47
54
  // medium is deprecated, so we map it to small
48
55
  const size = sizeProp === Size.MEDIUM ? Size.SMALL : sizeProp;
@@ -56,7 +63,7 @@ const Badge = ({
56
63
  );
57
64
 
58
65
  return (
59
- <div aria-label={ariaLabel} className={classes}>
66
+ <div aria-label={ariaLabel} className={classes} style={style}>
60
67
  <div className="tw-badge__children">{children}</div>
61
68
  <Circle size={mapLegacySize[size]} fixedSize className="tw-badge__content">
62
69
  {badge}
@@ -0,0 +1,65 @@
1
+ import StatusIcon, { StatusIconProps } from '../statusIcon';
2
+ import { Flag } from '@wise/art';
3
+ import Circle, { CircleProps } from '../common/circle';
4
+ import Image from '../image';
5
+ import type { BadgeProps } from './Badge';
6
+ import { Plus } from '@transferwise/icons';
7
+ import { Size } from '../common';
8
+
9
+ export type Props = {
10
+ status?: StatusIconProps['sentiment'];
11
+ flagCode?: string;
12
+ imgSrc?: string;
13
+ icon?: React.ReactNode;
14
+ type?: 'action' | 'reference';
15
+ size?: BadgeProps['size'];
16
+ };
17
+
18
+ /**
19
+ * Common pre-built badge variants.
20
+ */
21
+ export default function BadgeAssets({
22
+ status,
23
+ flagCode,
24
+ imgSrc,
25
+ icon = null,
26
+ type = 'action',
27
+ size: sizeProp,
28
+ }: Props) {
29
+ // map to old size (sm, lg) to new ones (numeric)
30
+ const size: CircleProps['size'] = sizeProp === Size.LARGE ? 24 : 16;
31
+ if (status) {
32
+ return <StatusIcon sentiment={status} size={size} />;
33
+ }
34
+ if (flagCode) {
35
+ return (
36
+ <Circle size={size} fixedSize enableBorder>
37
+ <Flag code={flagCode} intrinsicSize={size} />
38
+ </Circle>
39
+ );
40
+ }
41
+ if (imgSrc) {
42
+ return (
43
+ <Circle size={size} fixedSize enableBorder>
44
+ <Image src={imgSrc} alt="" />
45
+ </Circle>
46
+ );
47
+ }
48
+ if (['action', 'reference'].includes(type)) {
49
+ return (
50
+ <Circle
51
+ size={size}
52
+ fixedSize
53
+ style={{
54
+ backgroundColor:
55
+ type === 'action'
56
+ ? 'var(--color-interactive-accent)'
57
+ : 'var(--color-background-neutral)',
58
+ }}
59
+ >
60
+ {icon ?? <Plus />}
61
+ </Circle>
62
+ );
63
+ }
64
+ return null;
65
+ }
@@ -1,2 +1,5 @@
1
1
  export { default } from './Badge';
2
2
  export type { BadgeProps } from './Badge';
3
+
4
+ export { default as BadgeAssets } from './BadgeAssets';
5
+ export type { Props as BadgeAssetsProps } from './BadgeAssets';
@@ -4,4 +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);
16
+ }
17
+ .np-circle .tw-icon > svg {
18
+ height: var(--circle-icon-size);
19
+ width: var(--circle-icon-size);
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);
7
43
  }
@@ -3,4 +3,45 @@
3
3
  width: var(--circle-size);
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
+ // sets custom typography styles 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
+ // sets custom typography styles for Wise Sand font
16
+ .np-display {
17
+ font-size: var(--circle-font-size);
18
+ }
19
+
20
+ // has custom icon sizes
21
+ .tw-icon > svg {
22
+ height: var(--circle-icon-size);
23
+ width: var(--circle-icon-size);
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
+ }
47
+ }
@@ -1,7 +1,7 @@
1
1
  import { HTMLAttributes, forwardRef } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
 
4
- export type ShapeSize = 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72;
4
+ export type ShapeSize = 16 | 24 | 32 | 40 | 48 | 56 | 72;
5
5
 
6
6
  export type Props = {
7
7
  /**
@@ -17,14 +17,42 @@ export type Props = {
17
17
  * as those can be dynamic a at certain viewport sizes
18
18
  */
19
19
  fixedSize?: boolean;
20
+ enableBorder?: boolean;
20
21
  } & HTMLAttributes<HTMLDivElement>;
21
22
 
23
+ /**
24
+ * circle like components has custom sizes for icons
25
+ */
26
+ const MAP_ICON_SIZE = {
27
+ 16: 12,
28
+ 24: 16,
29
+ 32: 18,
30
+ 40: 20,
31
+ 48: 24,
32
+ 56: 28,
33
+ 72: 36,
34
+ };
35
+
36
+ /**
37
+ * circle like components have custom font styles
38
+ */
39
+ const MAP_FONT_SIZE = {
40
+ 16: 8,
41
+ 24: 12,
42
+ 32: 14,
43
+ 40: 18,
44
+ 48: 22,
45
+ 56: 26,
46
+ 72: 30,
47
+ };
48
+
22
49
  const Circle = forwardRef(function Circle(
23
50
  {
24
51
  as: Element = 'div',
25
52
  children,
26
53
  size = 48,
27
54
  fixedSize = false,
55
+ enableBorder = false,
28
56
  className,
29
57
  style,
30
58
  ...props
@@ -35,8 +63,18 @@ const Circle = forwardRef(function Circle(
35
63
  <Element
36
64
  {...props}
37
65
  ref={ref}
38
- style={{ ...style, '--circle-size': fixedSize ? `${size}px` : `var(--size-${size})` }}
39
- className={clsx('np-circle', 'd-flex align-items-center justify-content-center', className)}
66
+ style={{
67
+ '--circle-size': fixedSize ? `${size}px` : `var(--size-${size})`,
68
+ '--circle-icon-size': `${MAP_ICON_SIZE[size]}px`,
69
+ '--circle-font-size': `${MAP_FONT_SIZE[size]}px`,
70
+ ...style,
71
+ }}
72
+ className={clsx(
73
+ 'np-circle',
74
+ { 'np-circle-border': enableBorder },
75
+ 'd-flex align-items-center justify-content-center',
76
+ className,
77
+ )}
40
78
  >
41
79
  {children}
42
80
  </Element>
@@ -5,4 +5,3 @@ export { getMonthNames } from './getMonthNames/getMonthNames';
5
5
  export { isDateValid } from './isDateValid/isDateValid';
6
6
  export { isMonthAndYearFormat } from './isMonthAndYearFormat/isMonthAndYearFormat';
7
7
  export { MDY, YMD } from './getFormatForLocale/getFormatForLocale';
8
- export { returnDateView } from './getDateView/getDateView';