@transferwise/components 0.0.0-experimental-e5e0684 → 0.0.0-experimental-37d8e2c

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 (151) hide show
  1. package/build/avatar/Avatar.js +4 -1
  2. package/build/avatar/Avatar.js.map +1 -1
  3. package/build/avatar/Avatar.mjs +4 -1
  4. package/build/avatar/Avatar.mjs.map +1 -1
  5. package/build/avatar/colors/colors.js +1 -1
  6. package/build/avatar/colors/colors.js.map +1 -1
  7. package/build/avatar/colors/colors.mjs +1 -1
  8. package/build/avatar/colors/colors.mjs.map +1 -1
  9. package/build/avatarView/AvatarView.js +207 -0
  10. package/build/avatarView/AvatarView.js.map +1 -0
  11. package/build/avatarView/AvatarView.mjs +205 -0
  12. package/build/avatarView/AvatarView.mjs.map +1 -0
  13. package/build/avatarWrapper/AvatarWrapper.js +6 -9
  14. package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
  15. package/build/avatarWrapper/AvatarWrapper.mjs +5 -8
  16. package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
  17. package/build/badge/Badge.js +6 -1
  18. package/build/badge/Badge.js.map +1 -1
  19. package/build/badge/Badge.mjs +6 -1
  20. package/build/badge/Badge.mjs.map +1 -1
  21. package/build/circularButton/CircularButton.js +4 -7
  22. package/build/circularButton/CircularButton.js.map +1 -1
  23. package/build/circularButton/CircularButton.mjs +4 -7
  24. package/build/circularButton/CircularButton.mjs.map +1 -1
  25. package/build/common/circle/Circle.js +24 -1
  26. package/build/common/circle/Circle.js.map +1 -1
  27. package/build/common/circle/Circle.mjs +24 -1
  28. package/build/common/circle/Circle.mjs.map +1 -1
  29. package/build/common/initials.js +12 -0
  30. package/build/common/initials.js.map +1 -0
  31. package/build/common/initials.mjs +10 -0
  32. package/build/common/initials.mjs.map +1 -0
  33. package/build/common/notificationDot/NotificationDot.js +16 -0
  34. package/build/common/notificationDot/NotificationDot.js.map +1 -0
  35. package/build/common/notificationDot/NotificationDot.mjs +14 -0
  36. package/build/common/notificationDot/NotificationDot.mjs.map +1 -0
  37. package/build/display/Display.js +1 -1
  38. package/build/display/Display.js.map +1 -1
  39. package/build/display/Display.mjs +1 -1
  40. package/build/display/Display.mjs.map +1 -1
  41. package/build/index.js +6 -0
  42. package/build/index.js.map +1 -1
  43. package/build/index.mjs +3 -0
  44. package/build/index.mjs.map +1 -1
  45. package/build/main.css +82 -41
  46. package/build/statusIcon/StatusIcon.js +4 -2
  47. package/build/statusIcon/StatusIcon.js.map +1 -1
  48. package/build/statusIcon/StatusIcon.mjs +4 -2
  49. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  50. package/build/styles/avatarGroup/AvatarGroup.css +28 -0
  51. package/build/styles/avatarView/AvatarView.css +22 -0
  52. package/build/styles/badge/Badge.css +5 -5
  53. package/build/styles/circularButton/CircularButton.css +0 -24
  54. package/build/styles/common/circle/Circle.css +17 -0
  55. package/build/styles/common/notificationDot/NotificationDot.css +10 -0
  56. package/build/styles/main.css +82 -41
  57. package/build/styles/statusIcon/StatusIcon.css +0 -12
  58. package/build/typeahead/util/highlight.js +14 -2
  59. package/build/typeahead/util/highlight.js.map +1 -1
  60. package/build/typeahead/util/highlight.mjs +15 -3
  61. package/build/typeahead/util/highlight.mjs.map +1 -1
  62. package/build/types/avatar/Avatar.d.ts +3 -0
  63. package/build/types/avatar/Avatar.d.ts.map +1 -1
  64. package/build/types/avatar/colors/colors.d.ts +1 -1
  65. package/build/types/avatar/colors/colors.d.ts.map +1 -1
  66. package/build/types/avatarGroup/AvatarGroup.d.ts +19 -0
  67. package/build/types/avatarGroup/AvatarGroup.d.ts.map +1 -0
  68. package/build/types/avatarGroup/index.d.ts +3 -0
  69. package/build/types/avatarGroup/index.d.ts.map +1 -0
  70. package/build/types/avatarView/AvatarView.d.ts +25 -0
  71. package/build/types/avatarView/AvatarView.d.ts.map +1 -0
  72. package/build/types/avatarView/index.d.ts +3 -0
  73. package/build/types/avatarView/index.d.ts.map +1 -0
  74. package/build/types/avatarWrapper/AvatarWrapper.d.ts +3 -0
  75. package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
  76. package/build/types/badge/Badge.d.ts +2 -1
  77. package/build/types/badge/Badge.d.ts.map +1 -1
  78. package/build/types/circularButton/CircularButton.d.ts.map +1 -1
  79. package/build/types/common/circle/Circle.d.ts +1 -1
  80. package/build/types/common/circle/Circle.d.ts.map +1 -1
  81. package/build/types/common/index.d.ts +1 -0
  82. package/build/types/common/initials.d.ts +2 -0
  83. package/build/types/common/initials.d.ts.map +1 -0
  84. package/build/types/common/notificationDot/NotificationDot.d.ts +8 -0
  85. package/build/types/common/notificationDot/NotificationDot.d.ts.map +1 -0
  86. package/build/types/index.d.ts +4 -1
  87. package/build/types/index.d.ts.map +1 -1
  88. package/build/types/statusIcon/StatusIcon.d.ts +6 -2
  89. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  90. package/build/types/typeahead/util/highlight.d.ts +1 -1
  91. package/build/types/typeahead/util/highlight.d.ts.map +1 -1
  92. package/build/types/uploadInput/UploadInput.d.ts +0 -9
  93. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  94. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +1 -3
  95. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  96. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  97. package/build/uploadInput/UploadInput.js +51 -57
  98. package/build/uploadInput/UploadInput.js.map +1 -1
  99. package/build/uploadInput/UploadInput.mjs +51 -57
  100. package/build/uploadInput/UploadInput.mjs.map +1 -1
  101. package/build/uploadInput/uploadItem/UploadItem.js +3 -8
  102. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  103. package/build/uploadInput/uploadItem/UploadItem.mjs +3 -8
  104. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  105. package/build/uploadInput/uploadItem/UploadItemLink.js +0 -1
  106. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  107. package/build/uploadInput/uploadItem/UploadItemLink.mjs +0 -1
  108. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  109. package/package.json +3 -3
  110. package/src/avatar/Avatar.tsx +4 -1
  111. package/src/avatar/colors/colors.ts +4 -4
  112. package/src/avatarGroup/AvatarGroup.css +28 -0
  113. package/src/avatarGroup/AvatarGroup.less +32 -0
  114. package/src/avatarGroup/AvatarGroup.story.tsx +249 -0
  115. package/src/avatarGroup/AvatarGroup.tsx +78 -0
  116. package/src/avatarGroup/index.ts +2 -0
  117. package/src/avatarView/AvatarView.css +22 -0
  118. package/src/avatarView/AvatarView.less +24 -0
  119. package/src/avatarView/AvatarView.story.tsx +425 -0
  120. package/src/avatarView/AvatarView.tsx +217 -0
  121. package/src/avatarView/index.ts +2 -0
  122. package/src/avatarWrapper/AvatarWrapper.tsx +12 -16
  123. package/src/badge/Badge.css +5 -5
  124. package/src/badge/Badge.less +3 -3
  125. package/src/badge/Badge.tsx +10 -1
  126. package/src/circularButton/CircularButton.css +0 -24
  127. package/src/circularButton/CircularButton.less +15 -15
  128. package/src/circularButton/CircularButton.tsx +4 -5
  129. package/src/common/circle/Circle.css +17 -0
  130. package/src/common/circle/Circle.less +22 -0
  131. package/src/common/circle/Circle.tsx +33 -2
  132. package/src/common/index.js +1 -0
  133. package/src/common/initials.ts +13 -0
  134. package/src/common/notificationDot/NotificationDot.css +10 -0
  135. package/src/common/notificationDot/NotificationDot.less +7 -0
  136. package/src/common/notificationDot/NotificationDot.tsx +10 -0
  137. package/src/display/Display.tsx +1 -1
  138. package/src/index.ts +4 -0
  139. package/src/main.css +82 -41
  140. package/src/main.less +3 -0
  141. package/src/statusIcon/StatusIcon.css +0 -12
  142. package/src/statusIcon/StatusIcon.less +6 -6
  143. package/src/statusIcon/StatusIcon.story.tsx +32 -28
  144. package/src/statusIcon/StatusIcon.tsx +18 -4
  145. package/src/test-utils/avatar-img.webp +0 -0
  146. package/src/typeahead/typeaheadOption/TypeaheadOption.spec.js +7 -5
  147. package/src/typeahead/util/highlight.tsx +7 -2
  148. package/src/uploadInput/UploadInput.spec.tsx +6 -150
  149. package/src/uploadInput/UploadInput.tsx +60 -81
  150. package/src/uploadInput/uploadItem/UploadItem.tsx +6 -12
  151. package/src/uploadInput/uploadItem/UploadItemLink.tsx +1 -9
@@ -0,0 +1,425 @@
1
+ /* eslint-disable react/jsx-key */
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+ import { storyConfig } from '../test-utils';
4
+ import {
5
+ Freeze,
6
+ Graph,
7
+ Leaf,
8
+ MassPayout,
9
+ Money,
10
+ Plane,
11
+ PlusCircle,
12
+ QrCode,
13
+ Rewards,
14
+ Target,
15
+ Team,
16
+ Transport,
17
+ Wallet,
18
+ Water,
19
+ } from '@transferwise/icons';
20
+ import { action } from '@storybook/addon-actions';
21
+ import { useState } from 'react';
22
+ import AvatarView, { AvatarViewProps } from '.';
23
+ import { Flag } from '@wise/art';
24
+ import Body from '../body';
25
+ import AvatarWrapper from '../avatarWrapper';
26
+ import { getInitials, Sentiment } from '../common';
27
+ import Display from '../display';
28
+ import { get } from 'lodash';
29
+ import { getAvatarColorFromSeed } from '../avatar/colors';
30
+
31
+ export default {
32
+ title: 'Content/AvatarView',
33
+ } satisfies Meta<typeof AvatarView>;
34
+
35
+ type Story = StoryObj<typeof AvatarView>;
36
+
37
+ const profileName1 = 'Wolter White';
38
+ const profileName2 = 'Tyler Durden';
39
+ const sizes: AvatarViewProps['size'][] = [16, 24, 32, 40, 48, 56, 72];
40
+
41
+ export const Selected: Story = storyConfig(
42
+ {
43
+ render: () => {
44
+ const [selected, setSelected] = useState(true);
45
+ return (
46
+ <div
47
+ style={{
48
+ gap: '1em',
49
+ display: 'grid',
50
+ justifyContent: 'space-between',
51
+ gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
52
+ }}
53
+ >
54
+ {sizes.map((size) => (
55
+ <AvatarView key={size} size={size} selected={selected}>
56
+ <Freeze />
57
+ </AvatarView>
58
+ ))}
59
+ {sizes.map((size) => (
60
+ <AvatarView
61
+ key={size}
62
+ size={size}
63
+ selected={selected}
64
+ action={{
65
+ onClick: () => {
66
+ action('interactive avatar');
67
+ setSelected(!selected);
68
+ },
69
+ }}
70
+ >
71
+ <Freeze />
72
+ </AvatarView>
73
+ ))}
74
+ </div>
75
+ );
76
+ },
77
+ },
78
+ { variants: ['light', 'dark'] },
79
+ );
80
+
81
+ export const Notification: Story = storyConfig(
82
+ {
83
+ render: () => (
84
+ <div
85
+ style={{
86
+ gap: '1em',
87
+ display: 'grid',
88
+ justifyContent: 'space-between',
89
+ gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
90
+ }}
91
+ >
92
+ {sizes.map((size) => (
93
+ <AvatarView key={size} size={size} notification>
94
+ <MassPayout />
95
+ </AvatarView>
96
+ ))}
97
+ {sizes.map((size) => (
98
+ <AvatarView
99
+ key={size}
100
+ size={size}
101
+ notification
102
+ action={{
103
+ onClick: () => action('interactive avatar'),
104
+ }}
105
+ >
106
+ <MassPayout />
107
+ </AvatarView>
108
+ ))}
109
+
110
+ {sizes.map((size) => (
111
+ <AvatarView key={size} size={size} notification action={{ href: '#' }}>
112
+ <MassPayout />
113
+ </AvatarView>
114
+ ))}
115
+ </div>
116
+ ),
117
+ },
118
+ { variants: ['light', 'dark'] },
119
+ );
120
+
121
+ export const Badge: Story = storyConfig(
122
+ {
123
+ render: () => {
124
+ const currencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CNY'];
125
+ const icons = [
126
+ <Plane />,
127
+ <QrCode />,
128
+ <Target />,
129
+ <Transport />,
130
+ <Wallet />,
131
+ <Water />,
132
+ <Team />,
133
+ ];
134
+ return (
135
+ <div
136
+ style={{
137
+ gap: '1em',
138
+ display: 'grid',
139
+ justifyContent: 'space-between',
140
+ gridTemplate: 'auto auto / repeat(7, min-content)',
141
+ }}
142
+ >
143
+ {sizes.map((size, index) => (
144
+ <AvatarView key={size} size={size} badge={<Flag code={currencies[index]} />}>
145
+ {icons[index]}
146
+ </AvatarView>
147
+ ))}
148
+
149
+ {sizes.map((size, index) => (
150
+ <AvatarView
151
+ key={size}
152
+ size={size}
153
+ badge={<Flag code={currencies[index]} />}
154
+ action={{ onClick: action('onClick') }}
155
+ entity={{ name: 'Test Name' }}
156
+ >
157
+ {icons[index]}
158
+ </AvatarView>
159
+ ))}
160
+ </div>
161
+ );
162
+ },
163
+ },
164
+ { variants: ['light', 'dark'] },
165
+ );
166
+
167
+ export const Interactive: Story = storyConfig(
168
+ {
169
+ render: () => {
170
+ return (
171
+ <div
172
+ style={{
173
+ gap: '1em',
174
+ display: 'grid',
175
+ justifyContent: 'space-between',
176
+ gridTemplate: 'auto auto / repeat(2, min-content)',
177
+ }}
178
+ >
179
+ <Body type="body-large-bold">Button</Body>
180
+ <Body type="body-large-bold">Link</Body>
181
+
182
+ <AvatarView
183
+ action={{ onClick: action('onClick') }}
184
+ entity={{
185
+ imageSrc: '../avatar-img.webp',
186
+ }}
187
+ />
188
+
189
+ <AvatarView
190
+ action={{ href: '#' }}
191
+ entity={{
192
+ imageSrc: '../avatar-img.webp',
193
+ }}
194
+ />
195
+
196
+ <AvatarView
197
+ action={{ onClick: action('onClick') }}
198
+ badge={<Flag code="EU" />}
199
+ entity={{
200
+ imageSrc: '../avatar-img.webp',
201
+ name: 'Test Name',
202
+ }}
203
+ />
204
+
205
+ <AvatarView
206
+ action={{ href: '#' }}
207
+ badge={<Flag code="EU" />}
208
+ entity={{
209
+ imageSrc: '../avatar-img.webp',
210
+ name: 'Test Name',
211
+ }}
212
+ />
213
+
214
+ <AvatarView action={{ onClick: action('onClick') }} entity={{ name: 'Test Name' }} />
215
+
216
+ <AvatarView action={{ href: '#' }} entity={{ name: 'Test Name' }} />
217
+
218
+ <AvatarView
219
+ action={{ onClick: action('onClick') }}
220
+ badge={<Flag code="EU" />}
221
+ entity={{ name: 'Test Name' }}
222
+ />
223
+
224
+ <AvatarView
225
+ action={{ href: '#' }}
226
+ badge={<Flag code="EU" />}
227
+ entity={{ name: 'Test Name' }}
228
+ />
229
+
230
+ <AvatarView action={{ onClick: action('onClick') }}>
231
+ <Leaf />
232
+ </AvatarView>
233
+
234
+ <AvatarView action={{ href: '#' }}>
235
+ <Leaf />
236
+ </AvatarView>
237
+
238
+ <AvatarView action={{ onClick: action('onClick') }} badge={<Flag code="EU" />}>
239
+ <Leaf />
240
+ </AvatarView>
241
+
242
+ <AvatarView action={{ href: '#' }} badge={<Flag code="EU" />}>
243
+ <Leaf />
244
+ </AvatarView>
245
+ </div>
246
+ );
247
+ },
248
+ },
249
+ { variants: ['light', 'dark'] },
250
+ );
251
+
252
+ export const Entity: Story = storyConfig(
253
+ {
254
+ render: () => {
255
+ return (
256
+ <div
257
+ style={{
258
+ gap: '1em',
259
+ display: 'grid',
260
+ justifyContent: 'space-between',
261
+ gridTemplate: 'auto auto / repeat(7, min-content)',
262
+ }}
263
+ >
264
+ {sizes.map((size) => (
265
+ <AvatarView
266
+ key={size}
267
+ size={size}
268
+ entity={{
269
+ imageSrc: '../avatar-img.webp',
270
+ name: 'Test Name',
271
+ profileType: 'business',
272
+ }}
273
+ />
274
+ ))}
275
+
276
+ {sizes.map((size) => (
277
+ <AvatarView
278
+ key={size}
279
+ size={size}
280
+ action={{ onClick: action('onClick') }}
281
+ entity={{
282
+ imageSrc: '../avatar-img.webp',
283
+ name: 'Test Name',
284
+ profileType: 'business',
285
+ }}
286
+ />
287
+ ))}
288
+
289
+ {sizes.map((size) => (
290
+ <AvatarView key={size} size={size} entity={{ name: 'Test Name' }} />
291
+ ))}
292
+
293
+ {sizes.map((size) => (
294
+ <AvatarView
295
+ key={size}
296
+ size={size}
297
+ action={{ onClick: action('onClick') }}
298
+ entity={{ name: 'Test Name' }}
299
+ />
300
+ ))}
301
+
302
+ {sizes.map((size) => (
303
+ <AvatarView key={size} size={size} entity={{ profileType: 'business' }} />
304
+ ))}
305
+
306
+ {sizes.map((size) => (
307
+ <AvatarView
308
+ key={size}
309
+ size={size}
310
+ action={{ onClick: action('onClick') }}
311
+ entity={{ profileType: 'business' }}
312
+ />
313
+ ))}
314
+
315
+ {sizes.map((size) => (
316
+ <AvatarView key={size} size={size} />
317
+ ))}
318
+
319
+ {sizes.map((size) => (
320
+ <AvatarView key={size} size={size} action={{ onClick: action('onClick') }} />
321
+ ))}
322
+ </div>
323
+ );
324
+ },
325
+ },
326
+ { variants: ['light', 'dark'] },
327
+ );
328
+
329
+ export const BrokenImageFallback: Story = {
330
+ render: () => <AvatarView entity={{ imageSrc: '../img-wrong-url.lol' }} />,
331
+ };
332
+
333
+ export const EdgeInstaces: Story = {
334
+ render: () => {
335
+ const css = `.custom-variant {
336
+ border: 1px dotted black;
337
+ }`;
338
+ return (
339
+ <div
340
+ style={{
341
+ gap: '1em',
342
+ display: 'grid',
343
+ justifyContent: 'space-between',
344
+ gridTemplate: 'auto auto / repeat(2, min-content)',
345
+ }}
346
+ >
347
+ <style>{css}</style>
348
+ {/* <Body>- Custom colors</Body> */}
349
+ <AvatarView
350
+ action={{ onClick: action('onClick') }}
351
+ badge={<Flag code="eu" />}
352
+ style={{ backgroundColor: 'var(--color-bright-orange)' }}
353
+ >
354
+ <Money />
355
+ </AvatarView>
356
+ <AvatarView
357
+ badge={<Flag code="eu" />}
358
+ style={{ backgroundColor: 'var(--color-bright-orange)' }}
359
+ >
360
+ <Money />
361
+ </AvatarView>
362
+ {/* <Body>- Add example with dashed avatar as in shared balances</Body> */}
363
+ <AvatarView action={{ onClick: action('onClick') }} className="custom-variant">
364
+ <PlusCircle />
365
+ </AvatarView>
366
+ <AvatarView action={{ href: '#' }} className="custom-variant">
367
+ <PlusCircle />
368
+ </AvatarView>
369
+ <AvatarView action={{ href: '#' }} className="custom-variant">
370
+ {null}
371
+ </AvatarView>
372
+ {/* <Body> - Add example custom backgrounds as in account summary (https://wise.com/account-summary)</Body> */}
373
+ <AvatarView style={{ backgroundColor: 'rgb(203, 217, 195)' }}>
374
+ <span style={{ color: 'rgb(22, 51, 0)' }}>
375
+ <Graph />
376
+ </span>
377
+ </AvatarView>
378
+ <AvatarView
379
+ action={{ onClick: action('onClikc') }}
380
+ style={{ backgroundColor: 'rgb(203, 217, 195)' }}
381
+ >
382
+ <span style={{ color: 'rgb(22, 51, 0)' }}>
383
+ <Graph />
384
+ </span>
385
+ </AvatarView>
386
+ <AvatarView style={{ backgroundColor: 'rgb(22, 51, 0)' }}>
387
+ <span style={{ color: 'rgb(203, 217, 195)' }}>
388
+ <Rewards />
389
+ </span>
390
+ </AvatarView>
391
+ <AvatarView
392
+ action={{ onClick: action('onClikc') }}
393
+ style={{ backgroundColor: 'rgb(22, 51, 0)' }}
394
+ >
395
+ <span style={{ color: 'rgb(203, 217, 195)' }}>
396
+ <Rewards />
397
+ </span>
398
+ </AvatarView>
399
+ {/* <Body>- Add example Wise Sand + custom background from hash</Body> */}
400
+ <AvatarView
401
+ badge={<Flag code="eu" />}
402
+ style={{ backgroundColor: getAvatarColorFromSeed(profileName1) }}
403
+ >
404
+ <Display>{getInitials(profileName1)}</Display>
405
+ </AvatarView>
406
+ <AvatarView style={{ backgroundColor: getAvatarColorFromSeed(profileName1) }}>
407
+ <Display>{getInitials(profileName1)}</Display>
408
+ </AvatarView>
409
+ <AvatarView
410
+ action={{ onClick: action('onClick') }}
411
+ style={{ backgroundColor: getAvatarColorFromSeed(profileName2) }}
412
+ >
413
+ <Display>{getInitials(profileName1)}</Display>
414
+ </AvatarView>
415
+ <AvatarView
416
+ action={{ href: '#' }}
417
+ badge={<Flag code="eu" />}
418
+ style={{ backgroundColor: getAvatarColorFromSeed(profileName2) }}
419
+ >
420
+ <Display>{getInitials(profileName1)}</Display>
421
+ </AvatarView>
422
+ </div>
423
+ );
424
+ },
425
+ };
@@ -0,0 +1,217 @@
1
+ import Badge from '../badge';
2
+ import { getInitials } from '../common';
3
+ import NotificationDot from '../common/notificationDot/NotificationDot';
4
+ import StatusIcon from '../statusIcon';
5
+ import Circle from '../common/circle';
6
+ import { HTMLAttributes, PropsWithChildren, useState } from 'react';
7
+ import { ActionOptions as ActionProps } from '../common/action/Action';
8
+ import { clsx } from 'clsx';
9
+ import { Briefcase, Profile } from '@transferwise/icons';
10
+
11
+ type InteractiveProps = Omit<ActionProps, 'text'> &
12
+ Pick<HTMLAttributes<HTMLDivElement>, 'className' | 'aria-labelledby'>;
13
+
14
+ const AvatarViewDefaultSize = 48;
15
+
16
+ export type Props = PropsWithChildren<{
17
+ size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
18
+ /** adds prebuilt notification dot via Badge component */
19
+ notification?: boolean;
20
+ /** adds custom badge to avatar */
21
+ badge?: React.ReactNode;
22
+ entity?: EntityProps;
23
+ /** changes background color to neutral and wraps component with button */
24
+ action?: InteractiveProps;
25
+ /** changes background color to neutral and adds predefined Badge */
26
+ selected?: boolean;
27
+ /** often use case to override `background-color` or `border` */
28
+ style?: React.CSSProperties;
29
+ }> &
30
+ Pick<HTMLAttributes<HTMLDivElement>, 'className' | 'style'>;
31
+
32
+ /** Non interactive avatar view has custom border width */
33
+ const mapNonInteractiveBorderWidth = {
34
+ 16: 0.667,
35
+ 24: 1,
36
+ 32: 1,
37
+ 40: 1.25,
38
+ 48: 1.5,
39
+ 56: 1.75,
40
+ 72: 2.25,
41
+ };
42
+
43
+ /** Selected state has custom border width */
44
+ const mapSelectedBorderWidth = {
45
+ 16: 1,
46
+ 24: 1,
47
+ 32: 1,
48
+ 40: 2,
49
+ 48: 2,
50
+ 56: 2,
51
+ 72: 2,
52
+ };
53
+
54
+ function AvatarView({
55
+ children = undefined,
56
+ size = AvatarViewDefaultSize,
57
+ selected,
58
+ notification,
59
+ badge,
60
+ entity,
61
+ action,
62
+ className,
63
+ style,
64
+ }: Props) {
65
+ return (
66
+ <Interactive
67
+ {...action}
68
+ className={clsx('np-avatar-view', { 'np-avatar-view-selected': selected }, className)}
69
+ >
70
+ <Badges asset={badge} avatarProps={{ size, notification, selected }}>
71
+ <Circle
72
+ size={size}
73
+ fixedSize
74
+ style={{
75
+ // @ts-expect-error CSS custom props allowed
76
+ '--avatar-view-selected-border-width': `${mapSelectedBorderWidth[size]}px`,
77
+ '--avatar-view-non-interactive-border-width': `${mapNonInteractiveBorderWidth[size]}px`,
78
+ ...style,
79
+ }}
80
+ >
81
+ {children === undefined ? <Entity {...entity} /> : children}
82
+ </Circle>
83
+ </Badges>
84
+ </Interactive>
85
+ );
86
+ }
87
+
88
+ function Interactive({
89
+ children,
90
+ onClick,
91
+ href,
92
+ target,
93
+ className,
94
+ 'aria-label': ariaLabel,
95
+ 'aria-labelledby': ariaLabelledby,
96
+ }: PropsWithChildren<InteractiveProps>) {
97
+ const elementName: React.ElementType | null = onClick ? 'button' : href ? 'a' : null;
98
+ if (elementName === null) {
99
+ // non interactive
100
+ return <div className={className}>{children}</div>;
101
+ }
102
+ const a11yProps = { ariaLabel, ariaLabelledby };
103
+ if (elementName === 'button') {
104
+ return (
105
+ <button
106
+ type="button"
107
+ className={clsx(className, 'np-avatar-view-interactive', 'btn-unstyled')}
108
+ onClick={onClick}
109
+ {...a11yProps}
110
+ >
111
+ {children}
112
+ </button>
113
+ );
114
+ }
115
+ if (elementName === 'a') {
116
+ return (
117
+ // eslint-disable-next-line react/jsx-no-target-blank
118
+ <a
119
+ href={href}
120
+ target={target}
121
+ rel={target === '_blank' ? 'noreferrer' : undefined}
122
+ className={clsx(className, 'np-avatar-view-interactive', 'text-no-decoration')}
123
+ {...a11yProps}
124
+ >
125
+ {children}
126
+ </a>
127
+ );
128
+ }
129
+ }
130
+
131
+ const mapBadgeSize = {
132
+ 16: 'sm',
133
+ 24: 'sm',
134
+ 32: 'sm',
135
+ 40: 'sm',
136
+ 48: 'sm',
137
+ 56: 'lg',
138
+ 72: 'lg',
139
+ };
140
+
141
+ /** AvatarView has a custom sizes for badge */
142
+ const mapNotificationSize = {
143
+ 16: 6,
144
+ 24: 8,
145
+ 32: 10,
146
+ 40: 10,
147
+ 48: 14,
148
+ 56: 16,
149
+ 72: 20,
150
+ };
151
+
152
+ type BadgesProps = PropsWithChildren<{
153
+ asset: React.ReactNode;
154
+ avatarProps: Pick<Props, 'selected' | 'size' | 'notification'>;
155
+ }>;
156
+
157
+ function Badges({ children, asset, avatarProps }: BadgesProps) {
158
+ const { size = AvatarViewDefaultSize, selected, notification } = avatarProps;
159
+ if (asset || selected || notification) {
160
+ if (asset && size > 16) {
161
+ return (
162
+ <Badge badge={asset} size={mapBadgeSize[size]}>
163
+ {children}
164
+ </Badge>
165
+ );
166
+ }
167
+ if (selected && size > 16) {
168
+ return (
169
+ <Badge
170
+ badge={<StatusIcon sentiment="positive" size={mapBadgeSize[size]} />}
171
+ size={mapBadgeSize[size]}
172
+ >
173
+ {children}
174
+ </Badge>
175
+ );
176
+ }
177
+ if (notification) {
178
+ return (
179
+ <Badge
180
+ badge={
181
+ <NotificationDot
182
+ // @ts-expect-error CSS custom props allowed
183
+ style={{ '--badge-notification-dot-size': `${mapNotificationSize[size]}px` }}
184
+ />
185
+ }
186
+ position="top-right"
187
+ size={mapBadgeSize[size]}
188
+ >
189
+ {children}
190
+ </Badge>
191
+ );
192
+ }
193
+ }
194
+ return children;
195
+ }
196
+
197
+ export type EntityProps = {
198
+ imageSrc?: string | null;
199
+ name?: string | null;
200
+ profileType?: 'personal' | 'business' | null;
201
+ };
202
+
203
+ function Entity({ imageSrc = null, name = null, profileType = null }: EntityProps) {
204
+ const [tryLoadImage, setTryLoadImage] = useState(true);
205
+ if (imageSrc && tryLoadImage) {
206
+ return <img src={imageSrc} alt="" onError={() => setTryLoadImage(false)} />;
207
+ }
208
+ if (profileType) {
209
+ return profileType === 'business' ? <Briefcase /> : <Profile />;
210
+ }
211
+ if (name) {
212
+ return <>{getInitials(name)}</>;
213
+ }
214
+ return <Profile />;
215
+ }
216
+
217
+ export default AvatarView;
@@ -0,0 +1,2 @@
1
+ export type { Props as AvatarViewProps } from './AvatarView';
2
+ export { default } from './AvatarView';
@@ -3,7 +3,14 @@ import { useState, useEffect } from 'react';
3
3
 
4
4
  import Avatar, { AvatarProps, AvatarType } from '../avatar';
5
5
  import Badge, { BadgeProps } from '../badge';
6
- import { ProfileType, ProfileTypePersonal, ProfileTypeBusiness, Size, Sentiment } from '../common';
6
+ import {
7
+ ProfileType,
8
+ ProfileTypePersonal,
9
+ ProfileTypeBusiness,
10
+ Size,
11
+ Sentiment,
12
+ getInitials,
13
+ } from '../common';
7
14
  import StatusIcon from '../statusIcon/StatusIcon';
8
15
 
9
16
  interface OptionalBadgeProps extends Omit<BadgeProps, 'badge'> {
@@ -68,6 +75,9 @@ export type AvatarWrapperProps = {
68
75
  }
69
76
  );
70
77
 
78
+ /**
79
+ * @deprecated Use `AvatarView` component instead
80
+ */
71
81
  const AvatarWrapper = ({
72
82
  url,
73
83
  'aria-label': ariaLabel,
@@ -109,7 +119,7 @@ const AvatarWrapper = ({
109
119
  return {
110
120
  type: AvatarType.INITIALS,
111
121
  children: <>{getInitials(name)}</>,
112
- backgroundColorSeed: profileId?.toString(),
122
+ backgroundColorSeed: name?.toString(),
113
123
  ...updatedAvatarProps,
114
124
  };
115
125
  }
@@ -133,18 +143,4 @@ const AvatarWrapper = ({
133
143
  );
134
144
  };
135
145
 
136
- function getInitials(name: string) {
137
- const allInitials = name
138
- .split(' ')
139
- .map((part) => part[0])
140
- .join('')
141
- .toUpperCase();
142
-
143
- if (allInitials.length === 1) {
144
- return allInitials[0];
145
- }
146
-
147
- return allInitials[0] + allInitials.slice(-1);
148
- }
149
-
150
146
  export default AvatarWrapper;