@transferwise/components 46.80.0 → 46.81.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.
Files changed (92) 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 +175 -0
  6. package/build/avatarView/AvatarView.js.map +1 -0
  7. package/build/avatarView/AvatarView.mjs +173 -0
  8. package/build/avatarView/AvatarView.mjs.map +1 -0
  9. package/build/avatarView/NotificationDot.js +59 -0
  10. package/build/avatarView/NotificationDot.js.map +1 -0
  11. package/build/avatarView/NotificationDot.mjs +57 -0
  12. package/build/avatarView/NotificationDot.mjs.map +1 -0
  13. package/build/avatarWrapper/AvatarWrapper.js +10 -4
  14. package/build/avatarWrapper/AvatarWrapper.js.map +1 -1
  15. package/build/avatarWrapper/AvatarWrapper.mjs +10 -4
  16. package/build/avatarWrapper/AvatarWrapper.mjs.map +1 -1
  17. package/build/badge/Badge.js +16 -4
  18. package/build/badge/Badge.js.map +1 -1
  19. package/build/badge/Badge.mjs +15 -3
  20. package/build/badge/Badge.mjs.map +1 -1
  21. package/build/badge/BadgeAssets.js +60 -0
  22. package/build/badge/BadgeAssets.js.map +1 -0
  23. package/build/badge/BadgeAssets.mjs +58 -0
  24. package/build/badge/BadgeAssets.mjs.map +1 -0
  25. package/build/common/circle/Circle.js +19 -1
  26. package/build/common/circle/Circle.js.map +1 -1
  27. package/build/common/circle/Circle.mjs +19 -1
  28. package/build/common/circle/Circle.mjs.map +1 -1
  29. package/build/i18n/zh-HK.json +5 -0
  30. package/build/i18n/zh-HK.json.js +5 -0
  31. package/build/i18n/zh-HK.json.js.map +1 -1
  32. package/build/i18n/zh-HK.json.mjs +5 -0
  33. package/build/i18n/zh-HK.json.mjs.map +1 -1
  34. package/build/index.js +18 -13
  35. package/build/index.js.map +1 -1
  36. package/build/index.mjs +10 -7
  37. package/build/index.mjs.map +1 -1
  38. package/build/main.css +74 -5
  39. package/build/styles/avatarView/AvatarView.css +36 -0
  40. package/build/styles/avatarView/NotificationDot.css +20 -0
  41. package/build/styles/badge/Badge.css +6 -5
  42. package/build/styles/common/circle/Circle.css +32 -0
  43. package/build/styles/main.css +74 -5
  44. package/build/types/avatar/Avatar.d.ts +3 -0
  45. package/build/types/avatar/Avatar.d.ts.map +1 -1
  46. package/build/types/avatarView/AvatarView.d.ts +26 -0
  47. package/build/types/avatarView/AvatarView.d.ts.map +1 -0
  48. package/build/types/avatarView/NotificationDot.d.ts +8 -0
  49. package/build/types/avatarView/NotificationDot.d.ts.map +1 -0
  50. package/build/types/avatarView/index.d.ts +3 -0
  51. package/build/types/avatarView/index.d.ts.map +1 -0
  52. package/build/types/avatarWrapper/AvatarWrapper.d.ts +3 -0
  53. package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
  54. package/build/types/badge/Badge.d.ts +9 -4
  55. package/build/types/badge/Badge.d.ts.map +1 -1
  56. package/build/types/badge/BadgeAssets.d.ts +14 -0
  57. package/build/types/badge/BadgeAssets.d.ts.map +1 -0
  58. package/build/types/badge/index.d.ts +2 -0
  59. package/build/types/badge/index.d.ts.map +1 -1
  60. package/build/types/common/circle/Circle.d.ts +2 -0
  61. package/build/types/common/circle/Circle.d.ts.map +1 -1
  62. package/build/types/index.d.ts +3 -1
  63. package/build/types/index.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/src/avatar/Avatar.tsx +3 -0
  66. package/src/avatarView/AvatarView.css +36 -0
  67. package/src/avatarView/AvatarView.less +27 -0
  68. package/src/avatarView/AvatarView.story.tsx +467 -0
  69. package/src/avatarView/AvatarView.tsx +171 -0
  70. package/src/avatarView/NotificationDot.css +20 -0
  71. package/src/avatarView/NotificationDot.less +24 -0
  72. package/src/avatarView/NotificationDot.tsx +35 -0
  73. package/src/avatarView/index.ts +2 -0
  74. package/src/avatarWrapper/AvatarWrapper.story.tsx +19 -0
  75. package/src/avatarWrapper/AvatarWrapper.tsx +3 -0
  76. package/src/badge/Badge.css +6 -5
  77. package/src/badge/Badge.less +4 -3
  78. package/src/badge/Badge.tsx +20 -6
  79. package/src/badge/BadgeAssets.tsx +61 -0
  80. package/src/badge/index.ts +3 -0
  81. package/src/circularButton/CircularButton.spec.tsx +0 -36
  82. package/src/common/circle/Circle.css +32 -0
  83. package/src/common/circle/Circle.less +35 -0
  84. package/src/common/circle/Circle.tsx +24 -1
  85. package/src/flowNavigation/FlowNavigation.story.tsx +19 -52
  86. package/src/i18n/zh-HK.json +5 -0
  87. package/src/index.ts +3 -0
  88. package/src/listItem/ListItem.story.tsx +5 -47
  89. package/src/main.css +74 -5
  90. package/src/main.less +1 -0
  91. package/src/overlayHeader/OverlayHeader.story.tsx +6 -14
  92. package/src/circularButton/__snapshots__/CircularButton.spec.tsx.snap +0 -381
@@ -0,0 +1,467 @@
1
+ /* eslint-disable react/jsx-key */
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+ import {
4
+ Camera,
5
+ Convert,
6
+ FastFlag,
7
+ Freeze,
8
+ Graph,
9
+ Money,
10
+ Leaf,
11
+ Plane,
12
+ PlusCircle,
13
+ QrCode,
14
+ Rewards,
15
+ Target,
16
+ Team,
17
+ Transport,
18
+ Wallet,
19
+ Water,
20
+ } from '@transferwise/icons';
21
+ import AvatarView, { AvatarViewProps } from '.';
22
+ import { Flag } from '@wise/art';
23
+ import { getBrandColorFromSeed, getInitials, ProfileType } from '../common';
24
+ import Display from '../display';
25
+ import Body from '../body';
26
+
27
+ export default {
28
+ title: 'Content/AvatarView',
29
+ } satisfies Meta<typeof AvatarView>;
30
+
31
+ type Story = StoryObj<typeof AvatarView>;
32
+
33
+ const profileName1 = 'Wolter White';
34
+ const profileName2 = 'Tyler Durden';
35
+ const sizes: AvatarViewProps['size'][] = [16, 24, 32, 40, 48, 56, 72];
36
+
37
+ export const Selected: Story = {
38
+ tags: ['autodocs'],
39
+ render: () => {
40
+ return (
41
+ <div
42
+ style={{
43
+ gap: '1em',
44
+ display: 'grid',
45
+ justifyContent: 'space-between',
46
+ gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
47
+ }}
48
+ >
49
+ {sizes.map((size) => (
50
+ <Body type="body-large-bold">{size}</Body>
51
+ ))}
52
+ {sizes.map((size) => (
53
+ <AvatarView key={size} size={size} selected interactive>
54
+ <Freeze />
55
+ </AvatarView>
56
+ ))}
57
+ {sizes.map((size) => (
58
+ <AvatarView
59
+ key={size}
60
+ size={size}
61
+ imgSrc="../avatar-square-dude.webp"
62
+ selected
63
+ interactive
64
+ />
65
+ ))}
66
+ </div>
67
+ );
68
+ },
69
+ };
70
+
71
+ export const Notification: Story = {
72
+ tags: ['autodocs'],
73
+ render: () => (
74
+ <div
75
+ style={{
76
+ gap: '1em',
77
+ display: 'grid',
78
+ justifyContent: 'space-between',
79
+ gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
80
+ }}
81
+ >
82
+ {sizes.map((size) => (
83
+ <Body type="body-large-bold">{size}</Body>
84
+ ))}
85
+ {sizes.map((size) => (
86
+ <AvatarView key={size} size={size} notification>
87
+ <Freeze />
88
+ </AvatarView>
89
+ ))}
90
+ {sizes.map((size) => (
91
+ <AvatarView key={size} size={size} notification interactive>
92
+ <Freeze />
93
+ </AvatarView>
94
+ ))}
95
+ {sizes.map((size) => (
96
+ <AvatarView
97
+ key={size}
98
+ size={size}
99
+ imgSrc="../avatar-rectangle-fox.webp"
100
+ notification
101
+ interactive
102
+ />
103
+ ))}
104
+ </div>
105
+ ),
106
+ };
107
+
108
+ export const Badge: Story = {
109
+ tags: ['autodocs'],
110
+ render: () => {
111
+ const currencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CNY'];
112
+ const icons = [
113
+ <Plane />,
114
+ <QrCode />,
115
+ <Target />,
116
+ <Transport />,
117
+ <Wallet />,
118
+ <Water />,
119
+ <Team />,
120
+ ];
121
+ return (
122
+ <div
123
+ style={{
124
+ gap: '1em',
125
+ display: 'grid',
126
+ justifyContent: 'space-between',
127
+ gridTemplate: 'auto auto / repeat(7, min-content)',
128
+ }}
129
+ >
130
+ {sizes.map((size) => (
131
+ <Body type="body-large-bold">{size}</Body>
132
+ ))}
133
+ {sizes.map((size, index) => (
134
+ <AvatarView key={size} size={size} badge={{ flagCode: currencies[index] }}>
135
+ {icons[index]}
136
+ </AvatarView>
137
+ ))}
138
+
139
+ {sizes.map((size) => (
140
+ <AvatarView
141
+ key={size}
142
+ size={size}
143
+ imgSrc="../avatar-rectangle-fox.webp"
144
+ badge={{ imgSrc: '../tapestry-01.png' }}
145
+ />
146
+ ))}
147
+
148
+ {sizes.map((size) => (
149
+ <AvatarView
150
+ key={size}
151
+ size={size}
152
+ imgSrc="../avatar-square-dude.webp"
153
+ badge={{ imgSrc: '../tapestry-01.png' }}
154
+ />
155
+ ))}
156
+
157
+ {sizes.map((size, index) => (
158
+ <AvatarView key={size} size={size} badge={{ imgSrc: '../tapestry-01.png' }}>
159
+ {icons[index]}
160
+ </AvatarView>
161
+ ))}
162
+
163
+ {sizes.map((size, index) => (
164
+ <AvatarView key={size} size={size} badge={{ status: 'warning' }}>
165
+ {icons[index]}
166
+ </AvatarView>
167
+ ))}
168
+
169
+ {sizes.map((size, index) => (
170
+ <AvatarView key={size} size={size} badge={{ status: 'neutral' }}>
171
+ {icons[index]}
172
+ </AvatarView>
173
+ ))}
174
+
175
+ {sizes.map((size, index) => (
176
+ <AvatarView key={size} size={size} badge={{ status: 'negative' }}>
177
+ {icons[index]}
178
+ </AvatarView>
179
+ ))}
180
+
181
+ {sizes.map((size, index) => (
182
+ <AvatarView key={size} size={size} badge={{ icon: <FastFlag /> }}>
183
+ {icons[index]}
184
+ </AvatarView>
185
+ ))}
186
+
187
+ {sizes.map((size, index) => (
188
+ <AvatarView key={size} size={size} badge={{ type: 'reference' }}>
189
+ {icons[index]}
190
+ </AvatarView>
191
+ ))}
192
+
193
+ {sizes.map((size, index) => (
194
+ <AvatarView key={size} size={size} badge={{ type: 'action' }}>
195
+ {icons[index]}
196
+ </AvatarView>
197
+ ))}
198
+
199
+ {sizes.map((size) => (
200
+ <AvatarView
201
+ key={size}
202
+ size={size}
203
+ imgSrc="../avatar-square-dude.webp"
204
+ badge={{ type: 'action', icon: <Camera /> }}
205
+ />
206
+ ))}
207
+
208
+ {sizes.map((size) => (
209
+ <AvatarView
210
+ key={size}
211
+ size={size}
212
+ imgSrc="../avatar-square-dude.webp"
213
+ badge={{ type: 'reference', icon: <Convert /> }}
214
+ />
215
+ ))}
216
+
217
+ {sizes.map((size) => (
218
+ <AvatarView
219
+ key={size}
220
+ size={size}
221
+ imgSrc="../avatar-square-dude.webp"
222
+ badge={{ icon: <Convert /> }}
223
+ />
224
+ ))}
225
+ {sizes.map((size) => (
226
+ <AvatarView
227
+ key={size}
228
+ size={size}
229
+ imgSrc="../avatar-square-dude.webp"
230
+ badge={{
231
+ asset: (
232
+ <div
233
+ className="d-flex align-items-center justify-content-center"
234
+ style={{
235
+ backgroundColor: 'var(--color-bright-pink)',
236
+ color: 'var(--color-interactive-primary)',
237
+ width: '100%',
238
+ height: '100%',
239
+ }}
240
+ >
241
+ <Leaf />
242
+ </div>
243
+ ),
244
+ }}
245
+ />
246
+ ))}
247
+ </div>
248
+ );
249
+ },
250
+ };
251
+
252
+ export const Images: Story = {
253
+ tags: ['autodocs'],
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
+ <Body type="body-large-bold">{size}</Body>
266
+ ))}
267
+ {sizes.map((size) => (
268
+ <AvatarView
269
+ key={size}
270
+ size={size}
271
+ interactive
272
+ imgSrc="../avatar-square-dude.webp"
273
+ profileName="Test Name"
274
+ profileType={ProfileType.BUSINESS}
275
+ />
276
+ ))}
277
+ {sizes.map((size) => (
278
+ <AvatarView
279
+ key={size}
280
+ size={size}
281
+ imgSrc="../avatar-square-dude.webp"
282
+ profileName="Test Name"
283
+ profileType={ProfileType.BUSINESS}
284
+ />
285
+ ))}
286
+ {sizes.map((size) => (
287
+ <AvatarView key={size} size={size} interactive>
288
+ <Flag code="JPY" intrinsicSize={size} />
289
+ </AvatarView>
290
+ ))}
291
+ {sizes.map((size) => (
292
+ <AvatarView key={size} size={size}>
293
+ <Flag code="JPY" intrinsicSize={size} />
294
+ </AvatarView>
295
+ ))}
296
+ {sizes.map((size) => (
297
+ <AvatarView key={size} interactive imgSrc="../avatar-rectangle-fox.webp" size={size} />
298
+ ))}
299
+ {sizes.map((size) => (
300
+ <AvatarView key={size} size={size} imgSrc="../avatar-rectangle-fox.webp" />
301
+ ))}
302
+ </div>
303
+ );
304
+ },
305
+ };
306
+
307
+ export const Profiles: Story = {
308
+ tags: ['autodocs'],
309
+ render: () => {
310
+ return (
311
+ <div
312
+ style={{
313
+ gap: '1em',
314
+ display: 'grid',
315
+ justifyContent: 'space-between',
316
+ gridTemplate: 'auto auto / repeat(7, min-content)',
317
+ }}
318
+ >
319
+ {sizes.map((size) => (
320
+ <Body type="body-large-bold">{size}</Body>
321
+ ))}
322
+ {sizes.map((size) => (
323
+ <AvatarView
324
+ key={size}
325
+ size={size}
326
+ imgSrc="../avatar-square-dude.webp"
327
+ profileName="Test Name"
328
+ profileType={ProfileType.BUSINESS}
329
+ />
330
+ ))}
331
+
332
+ {sizes.map((size) => (
333
+ <AvatarView
334
+ key={size}
335
+ size={size}
336
+ interactive
337
+ imgSrc="../avatar-square-dude.webp"
338
+ profileName="Test Name"
339
+ profileType={ProfileType.BUSINESS}
340
+ />
341
+ ))}
342
+
343
+ {sizes.map((size) => (
344
+ <AvatarView key={size} size={size} profileName="Test Name" />
345
+ ))}
346
+
347
+ {sizes.map((size) => (
348
+ <AvatarView key={size} size={size} interactive profileName="Test Name" />
349
+ ))}
350
+
351
+ {sizes.map((size) => (
352
+ <AvatarView key={size} size={size} profileType={ProfileType.BUSINESS} />
353
+ ))}
354
+
355
+ {sizes.map((size) => (
356
+ <AvatarView key={size} size={size} interactive profileType={ProfileType.BUSINESS} />
357
+ ))}
358
+
359
+ {sizes.map((size) => (
360
+ <AvatarView key={size} size={size} />
361
+ ))}
362
+
363
+ {sizes.map((size) => (
364
+ <AvatarView key={size} size={size} interactive />
365
+ ))}
366
+ </div>
367
+ );
368
+ },
369
+ };
370
+
371
+ export const ProfileBrokenImageFallback: Story = {
372
+ parameters: {
373
+ chromatic: {
374
+ delay: 5000,
375
+ },
376
+ },
377
+ render: () => {
378
+ const assetUrl = 'https://test.com/img-wrong-url.test';
379
+ return (
380
+ <>
381
+ <AvatarView />
382
+ <AvatarView imgSrc={assetUrl} />
383
+ <AvatarView imgSrc={assetUrl} profileType={ProfileType.BUSINESS} />
384
+ <AvatarView imgSrc={assetUrl} profileType={ProfileType.BUSINESS} profileName="Test Name" />
385
+ </>
386
+ );
387
+ },
388
+ };
389
+
390
+ export const EdgeInstaces: Story = {
391
+ tags: ['autodocs'],
392
+ render: () => {
393
+ const css = `.custom-variant {
394
+ border: 1px dashed var(--color-border-neutral);
395
+ }`;
396
+ return (
397
+ <div
398
+ style={{
399
+ gap: '1em',
400
+ display: 'grid',
401
+ justifyContent: 'space-between',
402
+ gridTemplate: 'auto auto / repeat(2, min-content)',
403
+ }}
404
+ >
405
+ <style>{css}</style>
406
+ <AvatarView
407
+ interactive
408
+ badge={{ flagCode: 'EU' }}
409
+ style={{ backgroundColor: 'var(--color-bright-orange)' }}
410
+ >
411
+ <Money />
412
+ </AvatarView>
413
+ <AvatarView
414
+ badge={{ flagCode: 'EU' }}
415
+ style={{ backgroundColor: 'var(--color-bright-orange)', border: 'none' }}
416
+ >
417
+ <Money />
418
+ </AvatarView>
419
+ <AvatarView interactive className="custom-variant">
420
+ <PlusCircle />
421
+ </AvatarView>
422
+ <AvatarView interactive className="custom-variant">
423
+ {null}
424
+ </AvatarView>
425
+ <AvatarView style={{ color: 'rgb(22, 51, 0)', backgroundColor: 'rgb(203, 217, 195)' }}>
426
+ <Graph />
427
+ </AvatarView>
428
+ <AvatarView
429
+ interactive
430
+ style={{ color: 'rgb(22, 51, 0)', backgroundColor: 'rgb(203, 217, 195)' }}
431
+ >
432
+ <Graph />
433
+ </AvatarView>
434
+ <AvatarView style={{ color: 'rgb(203, 217, 195)', backgroundColor: 'rgb(22, 51, 0)' }}>
435
+ <Rewards />
436
+ </AvatarView>
437
+ <AvatarView
438
+ interactive
439
+ style={{ color: 'rgb(203, 217, 195)', backgroundColor: 'rgb(22, 51, 0)' }}
440
+ >
441
+ <Rewards />
442
+ </AvatarView>
443
+ <AvatarView
444
+ badge={{ flagCode: 'EU' }}
445
+ style={{ backgroundColor: getBrandColorFromSeed(profileName1) }}
446
+ >
447
+ <Display>{getInitials(profileName1)}</Display>
448
+ </AvatarView>
449
+ <AvatarView style={{ backgroundColor: getBrandColorFromSeed(profileName1) }}>
450
+ <Display>{getInitials(profileName1)}</Display>
451
+ </AvatarView>
452
+ <AvatarView interactive style={{ backgroundColor: getBrandColorFromSeed(profileName2) }}>
453
+ <Display>{getInitials(profileName1)}</Display>
454
+ </AvatarView>
455
+ <AvatarView
456
+ interactive
457
+ badge={{ flagCode: 'EU' }}
458
+ style={{ backgroundColor: getBrandColorFromSeed(profileName2) }}
459
+ >
460
+ <Display>{getInitials(profileName1)}</Display>
461
+ </AvatarView>
462
+ <AvatarView>9+</AvatarView>
463
+ <AvatarView interactive>5+</AvatarView>
464
+ </div>
465
+ );
466
+ },
467
+ };
@@ -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
+ }