@transferwise/components 46.111.1 → 46.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/avatarLayout/AvatarLayout.js.map +1 -1
- package/build/avatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/avatarView/AvatarView.js +27 -29
- package/build/avatarView/AvatarView.js.map +1 -1
- package/build/avatarView/AvatarView.mjs +27 -29
- package/build/avatarView/AvatarView.mjs.map +1 -1
- package/build/avatarView/{NotificationDot.js → Dot.js} +14 -12
- package/build/avatarView/Dot.js.map +1 -0
- package/build/avatarView/{NotificationDot.mjs → Dot.mjs} +14 -12
- package/build/avatarView/Dot.mjs.map +1 -0
- package/build/badge/BadgeAssets.js.map +1 -1
- package/build/badge/BadgeAssets.mjs.map +1 -1
- package/build/inputs/SelectInput.js +41 -2
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +41 -2
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/main.css +17 -11
- package/build/styles/avatarView/AvatarView.css +17 -11
- package/build/styles/avatarView/Dot.css +26 -0
- package/build/styles/main.css +17 -11
- package/build/types/avatarLayout/AvatarLayout.d.ts +1 -1
- package/build/types/avatarLayout/AvatarLayout.d.ts.map +1 -1
- package/build/types/avatarView/AvatarView.d.ts +1 -2
- package/build/types/avatarView/AvatarView.d.ts.map +1 -1
- package/build/types/avatarView/Dot.d.ts +8 -0
- package/build/types/avatarView/Dot.d.ts.map +1 -0
- package/build/types/badge/BadgeAssets.d.ts +1 -1
- package/build/types/badge/BadgeAssets.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +20 -1
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/avatarLayout/AvatarLayout.tsx +1 -1
- package/src/avatarView/AvatarView.css +17 -11
- package/src/avatarView/AvatarView.less +1 -1
- package/src/avatarView/AvatarView.story.tsx +92 -36
- package/src/avatarView/AvatarView.tsx +35 -30
- package/src/avatarView/Dot.css +26 -0
- package/src/avatarView/Dot.less +31 -0
- package/src/avatarView/Dot.tsx +42 -0
- package/src/badge/BadgeAssets.tsx +1 -1
- package/src/inputs/SelectInput.story.tsx +94 -0
- package/src/inputs/SelectInput.tsx +84 -1
- package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +89 -25
- package/src/main.css +17 -11
- package/build/avatarView/NotificationDot.js.map +0 -1
- package/build/avatarView/NotificationDot.mjs.map +0 -1
- package/build/styles/avatarView/NotificationDot.css +0 -20
- package/build/types/avatarView/NotificationDot.d.ts +0 -8
- package/build/types/avatarView/NotificationDot.d.ts.map +0 -1
- package/src/avatarView/NotificationDot.css +0 -20
- package/src/avatarView/NotificationDot.less +0 -24
- package/src/avatarView/NotificationDot.tsx +0 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transferwise/components",
|
|
3
|
-
"version": "46.
|
|
3
|
+
"version": "46.112.0",
|
|
4
4
|
"description": "Neptune React components",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"storybook-addon-tag-badges": "^2.0.2",
|
|
85
85
|
"storybook-addon-test-codegen": "^2.0.1",
|
|
86
86
|
"@transferwise/less-config": "3.1.2",
|
|
87
|
-
"@wise/components-theming": "1.7.0",
|
|
88
87
|
"@transferwise/neptune-css": "14.25.0",
|
|
88
|
+
"@wise/components-theming": "1.7.0",
|
|
89
89
|
"@wise/wds-configs": "0.0.0"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
@@ -4,7 +4,7 @@ import { useDirection } from '../common/hooks';
|
|
|
4
4
|
|
|
5
5
|
type SingleAvatarType = { asset?: AvatarViewProps['children'] } & Omit<
|
|
6
6
|
AvatarViewProps,
|
|
7
|
-
'
|
|
7
|
+
'selected' | 'size' | 'badge' | 'children' | 'interactive'
|
|
8
8
|
>;
|
|
9
9
|
|
|
10
10
|
export type Props = {
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
.np-
|
|
2
|
-
--np-
|
|
1
|
+
.np-dot {
|
|
2
|
+
--np-dot-size: 14px;
|
|
3
3
|
position: relative;
|
|
4
4
|
display: inline-block;
|
|
5
5
|
}
|
|
6
|
-
.np-
|
|
7
|
-
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-
|
|
8
|
-
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-
|
|
9
|
-
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-
|
|
10
|
-
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-
|
|
6
|
+
.np-dot-mask {
|
|
7
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
8
|
+
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
9
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
10
|
+
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
11
11
|
}
|
|
12
|
-
.np-
|
|
12
|
+
.np-dot-badge {
|
|
13
13
|
position: absolute;
|
|
14
|
-
width: var(--np-
|
|
15
|
-
height: var(--np-
|
|
16
|
-
background-color: var(--color-sentiment-negative);
|
|
14
|
+
width: var(--np-dot-size);
|
|
15
|
+
height: var(--np-dot-size);
|
|
17
16
|
border-radius: 9999px;
|
|
18
17
|
border-radius: var(--radius-full);
|
|
19
18
|
right: 0;
|
|
20
19
|
}
|
|
20
|
+
.np-dot-badge-notification {
|
|
21
|
+
background-color: var(--color-sentiment-negative);
|
|
22
|
+
}
|
|
23
|
+
.np-dot-badge-online {
|
|
24
|
+
background-color: #00a2dd;
|
|
25
|
+
background-color: var(--color-interactive-accent);
|
|
26
|
+
}
|
|
21
27
|
.np-avatar-view .np-avatar-view-content {
|
|
22
28
|
color: #37517e;
|
|
23
29
|
color: var(--color-content-primary);
|
|
@@ -67,42 +67,6 @@ export const Selected: Story = {
|
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
export const Notification: Story = {
|
|
71
|
-
render: () => (
|
|
72
|
-
<div
|
|
73
|
-
style={{
|
|
74
|
-
gap: '1em',
|
|
75
|
-
display: 'grid',
|
|
76
|
-
justifyContent: 'space-between',
|
|
77
|
-
gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
|
|
78
|
-
}}
|
|
79
|
-
>
|
|
80
|
-
{sizes.map((size) => (
|
|
81
|
-
<Body type="body-large-bold">{size}</Body>
|
|
82
|
-
))}
|
|
83
|
-
{sizes.map((size) => (
|
|
84
|
-
<AvatarView key={size} size={size} notification>
|
|
85
|
-
<Freeze />
|
|
86
|
-
</AvatarView>
|
|
87
|
-
))}
|
|
88
|
-
{sizes.map((size) => (
|
|
89
|
-
<AvatarView key={size} size={size} notification interactive>
|
|
90
|
-
<Freeze />
|
|
91
|
-
</AvatarView>
|
|
92
|
-
))}
|
|
93
|
-
{sizes.map((size) => (
|
|
94
|
-
<AvatarView
|
|
95
|
-
key={size}
|
|
96
|
-
size={size}
|
|
97
|
-
imgSrc="../avatar-rectangle-fox.webp"
|
|
98
|
-
notification
|
|
99
|
-
interactive
|
|
100
|
-
/>
|
|
101
|
-
))}
|
|
102
|
-
</div>
|
|
103
|
-
),
|
|
104
|
-
};
|
|
105
|
-
|
|
106
70
|
export const Badge: Story = {
|
|
107
71
|
render: () => {
|
|
108
72
|
const currencies = ['USD', 'EUR', 'GBP', 'AUD', 'CAD', 'JPY', 'CNY'];
|
|
@@ -219,6 +183,7 @@ export const Badge: Story = {
|
|
|
219
183
|
badge={{ icon: <Convert /> }}
|
|
220
184
|
/>
|
|
221
185
|
))}
|
|
186
|
+
|
|
222
187
|
{sizes.map((size) => (
|
|
223
188
|
<AvatarView
|
|
224
189
|
key={size}
|
|
@@ -241,11 +206,102 @@ export const Badge: Story = {
|
|
|
241
206
|
}}
|
|
242
207
|
/>
|
|
243
208
|
))}
|
|
209
|
+
|
|
210
|
+
{sizes.map((size) => (
|
|
211
|
+
<AvatarView
|
|
212
|
+
key={size}
|
|
213
|
+
size={size}
|
|
214
|
+
imgSrc="../avatar-square-dude.webp"
|
|
215
|
+
badge={{ type: 'notification' }}
|
|
216
|
+
/>
|
|
217
|
+
))}
|
|
218
|
+
|
|
219
|
+
{sizes.map((size) => (
|
|
220
|
+
<AvatarView
|
|
221
|
+
key={size}
|
|
222
|
+
size={size}
|
|
223
|
+
imgSrc="../avatar-square-dude.webp"
|
|
224
|
+
badge={{ type: 'online' }}
|
|
225
|
+
/>
|
|
226
|
+
))}
|
|
244
227
|
</div>
|
|
245
228
|
);
|
|
246
229
|
},
|
|
247
230
|
};
|
|
248
231
|
|
|
232
|
+
export const Notification: Story = {
|
|
233
|
+
render: () => (
|
|
234
|
+
<div
|
|
235
|
+
style={{
|
|
236
|
+
gap: '1em',
|
|
237
|
+
display: 'grid',
|
|
238
|
+
justifyContent: 'space-between',
|
|
239
|
+
gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
|
|
240
|
+
}}
|
|
241
|
+
>
|
|
242
|
+
{sizes.map((size) => (
|
|
243
|
+
<Body type="body-large-bold">{size}</Body>
|
|
244
|
+
))}
|
|
245
|
+
{sizes.map((size) => (
|
|
246
|
+
<AvatarView key={size} size={size} badge={{ type: 'notification' }}>
|
|
247
|
+
<Freeze />
|
|
248
|
+
</AvatarView>
|
|
249
|
+
))}
|
|
250
|
+
{sizes.map((size) => (
|
|
251
|
+
<AvatarView key={size} size={size} badge={{ type: 'notification' }} interactive>
|
|
252
|
+
<Freeze />
|
|
253
|
+
</AvatarView>
|
|
254
|
+
))}
|
|
255
|
+
{sizes.map((size) => (
|
|
256
|
+
<AvatarView
|
|
257
|
+
key={size}
|
|
258
|
+
size={size}
|
|
259
|
+
imgSrc="../avatar-rectangle-fox.webp"
|
|
260
|
+
badge={{ type: 'notification' }}
|
|
261
|
+
interactive
|
|
262
|
+
/>
|
|
263
|
+
))}
|
|
264
|
+
</div>
|
|
265
|
+
),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const Online: Story = {
|
|
269
|
+
tags: ['new'],
|
|
270
|
+
render: () => (
|
|
271
|
+
<div
|
|
272
|
+
style={{
|
|
273
|
+
gap: '1em',
|
|
274
|
+
display: 'grid',
|
|
275
|
+
justifyContent: 'space-between',
|
|
276
|
+
gridTemplate: `auto auto / repeat(${sizes.length}, min-content)`,
|
|
277
|
+
}}
|
|
278
|
+
>
|
|
279
|
+
{sizes.map((size) => (
|
|
280
|
+
<Body type="body-large-bold">{size}</Body>
|
|
281
|
+
))}
|
|
282
|
+
{sizes.map((size) => (
|
|
283
|
+
<AvatarView key={size} size={size} badge={{ type: 'online' }}>
|
|
284
|
+
<Freeze />
|
|
285
|
+
</AvatarView>
|
|
286
|
+
))}
|
|
287
|
+
{sizes.map((size) => (
|
|
288
|
+
<AvatarView key={size} size={size} badge={{ type: 'online' }} interactive>
|
|
289
|
+
<Freeze />
|
|
290
|
+
</AvatarView>
|
|
291
|
+
))}
|
|
292
|
+
{sizes.map((size) => (
|
|
293
|
+
<AvatarView
|
|
294
|
+
key={size}
|
|
295
|
+
size={size}
|
|
296
|
+
imgSrc="../avatar-rectangle-fox.webp"
|
|
297
|
+
badge={{ type: 'online' }}
|
|
298
|
+
interactive
|
|
299
|
+
/>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
),
|
|
303
|
+
};
|
|
304
|
+
|
|
249
305
|
export const Images: Story = {
|
|
250
306
|
render: () => {
|
|
251
307
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Badge, { BadgeAssets, BadgeProps, BadgeAssetsProps } from '../badge';
|
|
2
|
-
import
|
|
2
|
+
import Dot, { DotProps } from './Dot';
|
|
3
3
|
import Circle from '../common/circle';
|
|
4
4
|
import Image from '../image';
|
|
5
5
|
import { HTMLAttributes, PropsWithChildren, useState } from 'react';
|
|
@@ -26,7 +26,6 @@ export type Props = {
|
|
|
26
26
|
profileName?: string | null;
|
|
27
27
|
profileType?: ProfileTypeBusiness | ProfileTypePersonal;
|
|
28
28
|
size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
|
|
29
|
-
notification?: boolean;
|
|
30
29
|
badge?: AvatarViewBadgeProps;
|
|
31
30
|
interactive?: boolean;
|
|
32
31
|
selected?: boolean;
|
|
@@ -40,7 +39,6 @@ function AvatarView({
|
|
|
40
39
|
children = undefined,
|
|
41
40
|
size = 48,
|
|
42
41
|
selected,
|
|
43
|
-
notification,
|
|
44
42
|
badge,
|
|
45
43
|
interactive = false,
|
|
46
44
|
className,
|
|
@@ -62,7 +60,7 @@ function AvatarView({
|
|
|
62
60
|
)}
|
|
63
61
|
{...restProps}
|
|
64
62
|
>
|
|
65
|
-
<Badges avatar={{ size,
|
|
63
|
+
<Badges avatar={{ size, selected }} {...badge}>
|
|
66
64
|
<Circle
|
|
67
65
|
size={size}
|
|
68
66
|
fixedSize
|
|
@@ -115,7 +113,7 @@ const MAP_BADGE_POSITION = {
|
|
|
115
113
|
|
|
116
114
|
type BadgesProps = AvatarViewBadgeProps &
|
|
117
115
|
PropsWithChildren<{
|
|
118
|
-
avatar: Pick<Props, 'selected' | 'size'
|
|
116
|
+
avatar: Pick<Props, 'selected' | 'size'>;
|
|
119
117
|
}>;
|
|
120
118
|
|
|
121
119
|
/**
|
|
@@ -128,36 +126,43 @@ function Badges({
|
|
|
128
126
|
asset: customBadge,
|
|
129
127
|
...badgeAssets
|
|
130
128
|
}: BadgesProps) {
|
|
131
|
-
const { size = 48, selected
|
|
129
|
+
const { size = 48, selected } = avatar;
|
|
132
130
|
const anyBadge = Object.values({ customBadge, ...badgeAssets }).filter(Boolean).length > 0;
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
|
|
132
|
+
if ((!anyBadge && !selected) || size <= 16) {
|
|
133
|
+
return children;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (badgeAssets.type === 'notification' || badgeAssets.type === 'online') {
|
|
135
137
|
return (
|
|
136
|
-
<
|
|
137
|
-
aria-label={ariaLabel}
|
|
138
|
-
size={badgeSize}
|
|
139
|
-
badge={
|
|
140
|
-
customBadge ? (
|
|
141
|
-
<Circle fixedSize size={badgeSize}>
|
|
142
|
-
{customBadge}
|
|
143
|
-
</Circle>
|
|
144
|
-
) : (
|
|
145
|
-
<BadgeAssets {...(selected ? { status: 'positive' } : badgeAssets)} size={badgeSize} />
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
style={{
|
|
149
|
-
// @ts-expect-error CSS custom props allowed
|
|
150
|
-
'--badge-content-position': `${MAP_BADGE_POSITION[size] ?? 0}px`,
|
|
151
|
-
}}
|
|
152
|
-
>
|
|
138
|
+
<Dot avatarSize={size} variant={badgeAssets.type === 'online' ? 'online' : 'notification'}>
|
|
153
139
|
{children}
|
|
154
|
-
</
|
|
140
|
+
</Dot>
|
|
155
141
|
);
|
|
156
142
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
143
|
+
|
|
144
|
+
const badgeSize: BadgeAssetsProps['size'] = MAP_BADGE_ASSET_SIZE[size];
|
|
145
|
+
return (
|
|
146
|
+
<Badge
|
|
147
|
+
aria-label={ariaLabel}
|
|
148
|
+
size={badgeSize}
|
|
149
|
+
badge={
|
|
150
|
+
customBadge ? (
|
|
151
|
+
<Circle fixedSize size={badgeSize}>
|
|
152
|
+
{customBadge}
|
|
153
|
+
</Circle>
|
|
154
|
+
) : (
|
|
155
|
+
<BadgeAssets {...(selected ? { status: 'positive' } : badgeAssets)} size={badgeSize} />
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
style={{
|
|
159
|
+
// @ts-expect-error CSS custom props allowed
|
|
160
|
+
'--badge-content-position': `${MAP_BADGE_POSITION[size] ?? 0}px`,
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
{children}
|
|
164
|
+
</Badge>
|
|
165
|
+
);
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
function AvatarViewContent({
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.np-dot {
|
|
2
|
+
--np-dot-size: 14px;
|
|
3
|
+
position: relative;
|
|
4
|
+
display: inline-block;
|
|
5
|
+
}
|
|
6
|
+
.np-dot-mask {
|
|
7
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
8
|
+
mask-image: radial-gradient(circle at bottom calc(100% - (var(--np-dot-size) / 2)) left calc(100% - (var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
9
|
+
-webkit-mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
10
|
+
mask-image: radial-gradient(circle at bottom calc(100% - calc(var(--np-dot-size) / 2)) left calc(100% - calc(var(--np-dot-size) / 2)), transparent 0, transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)), black 0);
|
|
11
|
+
}
|
|
12
|
+
.np-dot-badge {
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: var(--np-dot-size);
|
|
15
|
+
height: var(--np-dot-size);
|
|
16
|
+
border-radius: 9999px;
|
|
17
|
+
border-radius: var(--radius-full);
|
|
18
|
+
right: 0;
|
|
19
|
+
}
|
|
20
|
+
.np-dot-badge-notification {
|
|
21
|
+
background-color: var(--color-sentiment-negative);
|
|
22
|
+
}
|
|
23
|
+
.np-dot-badge-online {
|
|
24
|
+
background-color: #00a2dd;
|
|
25
|
+
background-color: var(--color-interactive-accent);
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.np-dot {
|
|
2
|
+
--np-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-dot-size) / 2))
|
|
9
|
+
left calc(100% - calc(var(--np-dot-size) / 2)),
|
|
10
|
+
transparent 0,
|
|
11
|
+
transparent calc(var(--np-dot-size) / 2 + var(--np-dot-offset)),
|
|
12
|
+
black 0
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&-badge {
|
|
17
|
+
position: absolute;
|
|
18
|
+
width: var(--np-dot-size);
|
|
19
|
+
height: var(--np-dot-size);
|
|
20
|
+
border-radius: var(--radius-full);
|
|
21
|
+
right: 0;
|
|
22
|
+
|
|
23
|
+
&-notification {
|
|
24
|
+
background-color: var(--color-sentiment-negative);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&-online {
|
|
28
|
+
background-color: var(--color-interactive-accent);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { Props as AvatarViewProps } from './AvatarView';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
|
|
5
|
+
export type DotProps = Pick<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
avatarSize?: AvatarViewProps['size'];
|
|
7
|
+
variant?: 'notification' | 'online';
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Depending on avatar size, dot size and offset are different
|
|
12
|
+
*/
|
|
13
|
+
const MAP_STYLE_CONFIG = {
|
|
14
|
+
16: { size: 6, offset: 1 },
|
|
15
|
+
24: { size: 8, offset: 2 },
|
|
16
|
+
32: { size: 10, offset: 2 },
|
|
17
|
+
40: { size: 10, offset: 2 },
|
|
18
|
+
48: { size: 14, offset: 2 },
|
|
19
|
+
56: { size: 16, offset: 3 },
|
|
20
|
+
72: { size: 20, offset: 3 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default function Dot({ children, avatarSize = 48, variant = 'notification' }: DotProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className="np-dot"
|
|
27
|
+
style={{
|
|
28
|
+
// @ts-expect-error CSS custom props allowed
|
|
29
|
+
'--np-dot-size': `${MAP_STYLE_CONFIG[avatarSize].size}px`,
|
|
30
|
+
'--np-dot-offset': `${MAP_STYLE_CONFIG[avatarSize].offset}px`,
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
className={clsx('np-dot-badge', {
|
|
35
|
+
'np-dot-badge-notification': variant === 'notification',
|
|
36
|
+
'np-dot-badge-online': variant === 'online',
|
|
37
|
+
})}
|
|
38
|
+
/>
|
|
39
|
+
<div className="np-dot-mask">{children}</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -516,3 +516,97 @@ export const WithinModal: Story<Currency> = {
|
|
|
516
516
|
},
|
|
517
517
|
],
|
|
518
518
|
};
|
|
519
|
+
|
|
520
|
+
interface Country {
|
|
521
|
+
code: string;
|
|
522
|
+
name: string;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const countries: Country[] = [
|
|
526
|
+
{ code: 'US', name: 'United States' },
|
|
527
|
+
{ code: 'GB', name: 'United Kingdom' },
|
|
528
|
+
{ code: 'CA', name: 'Canada' },
|
|
529
|
+
{ code: 'AU', name: 'Australia' },
|
|
530
|
+
{ code: 'DE', name: 'Germany' },
|
|
531
|
+
{ code: 'FR', name: 'France' },
|
|
532
|
+
{ code: 'JP', name: 'Japan' },
|
|
533
|
+
{ code: 'BR', name: 'Brazil' },
|
|
534
|
+
{ code: 'IN', name: 'India' },
|
|
535
|
+
{ code: 'CN', name: 'China' },
|
|
536
|
+
{ code: 'IT', name: 'Italy' },
|
|
537
|
+
{ code: 'ES', name: 'Spain' },
|
|
538
|
+
{ code: 'NL', name: 'Netherlands' },
|
|
539
|
+
{ code: 'CH', name: 'Switzerland' },
|
|
540
|
+
{ code: 'SE', name: 'Sweden' },
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
function countryOption(country: Country) {
|
|
544
|
+
return {
|
|
545
|
+
type: 'option',
|
|
546
|
+
value: country.code,
|
|
547
|
+
filterMatchers: [country.code, country.name],
|
|
548
|
+
} satisfies SelectInputItem;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export const WithAutocomplete: Story<string> = {
|
|
552
|
+
args: {
|
|
553
|
+
name: 'country',
|
|
554
|
+
autocomplete: 'country-name',
|
|
555
|
+
placeholder: 'Select your country',
|
|
556
|
+
items: countries.map(countryOption),
|
|
557
|
+
renderValue: (countryCode, withinTrigger) => {
|
|
558
|
+
const country = countries.find((c) => c.code === countryCode);
|
|
559
|
+
return (
|
|
560
|
+
<SelectInputOptionContent
|
|
561
|
+
title={withinTrigger ? countryCode : country?.name || countryCode}
|
|
562
|
+
note={withinTrigger ? undefined : countryCode}
|
|
563
|
+
icon={<Flag code={countryCode} intrinsicSize={24} />}
|
|
564
|
+
/>
|
|
565
|
+
);
|
|
566
|
+
},
|
|
567
|
+
filterable: true,
|
|
568
|
+
filterPlaceholder: 'Type a country name',
|
|
569
|
+
size: 'lg',
|
|
570
|
+
},
|
|
571
|
+
render: function Render({ onChange, onClear, ...args }) {
|
|
572
|
+
const [selectedCountry, setSelectedCountry] = useState<string | undefined>(undefined);
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div>
|
|
576
|
+
<form
|
|
577
|
+
method="post"
|
|
578
|
+
onSubmit={(e) => {
|
|
579
|
+
e.preventDefault();
|
|
580
|
+
console.log(
|
|
581
|
+
`Form submitted with country: ${selectedCountry}. This saves data for browser autocomplete!`,
|
|
582
|
+
);
|
|
583
|
+
}}
|
|
584
|
+
>
|
|
585
|
+
<div>
|
|
586
|
+
<label htmlFor="country-select" className="block text-sm font-medium mb-2">
|
|
587
|
+
Country Selection with Autocomplete:
|
|
588
|
+
</label>
|
|
589
|
+
<SelectInput
|
|
590
|
+
{...args}
|
|
591
|
+
id="country-select"
|
|
592
|
+
value={selectedCountry}
|
|
593
|
+
onChange={(country) => {
|
|
594
|
+
setSelectedCountry(country);
|
|
595
|
+
onChange?.(country);
|
|
596
|
+
console.log('Country selected via SelectInput:', country);
|
|
597
|
+
}}
|
|
598
|
+
onClear={() => {
|
|
599
|
+
setSelectedCountry(undefined);
|
|
600
|
+
onClear?.();
|
|
601
|
+
}}
|
|
602
|
+
/>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<Button type="submit" v2 className="m-t-2" data-testid="submit-btn">
|
|
606
|
+
Submit Form
|
|
607
|
+
</Button>
|
|
608
|
+
</form>
|
|
609
|
+
</div>
|
|
610
|
+
);
|
|
611
|
+
},
|
|
612
|
+
};
|
|
@@ -155,6 +155,25 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
|
155
155
|
multiple?: M;
|
|
156
156
|
placeholder?: string;
|
|
157
157
|
items: readonly SelectInputItem<NonNullable<T>>[];
|
|
158
|
+
/**
|
|
159
|
+
* Enables browser autocomplete integration through the search input.
|
|
160
|
+
* Accepts standard HTML autocomplete values (e.g., "country-name", "address-level1").
|
|
161
|
+
*
|
|
162
|
+
* Requires `filterable={true}` to enable the search input.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* <SelectInput
|
|
166
|
+
* name="country"
|
|
167
|
+
* autocomplete="country-name"
|
|
168
|
+
* filterable={true}
|
|
169
|
+
* items={[{
|
|
170
|
+
* type: 'option',
|
|
171
|
+
* value: 'GB',
|
|
172
|
+
* filterMatchers: ['United Kingdom', 'UK']
|
|
173
|
+
* }]}
|
|
174
|
+
* />
|
|
175
|
+
*/
|
|
176
|
+
autocomplete?: string;
|
|
158
177
|
defaultValue?: M extends true ? readonly T[] : T;
|
|
159
178
|
value?: M extends true ? readonly T[] : T;
|
|
160
179
|
compareValues?:
|
|
@@ -257,6 +276,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
257
276
|
name,
|
|
258
277
|
multiple,
|
|
259
278
|
placeholder,
|
|
279
|
+
autocomplete,
|
|
260
280
|
items,
|
|
261
281
|
defaultValue,
|
|
262
282
|
value: controlledValue,
|
|
@@ -457,7 +477,15 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
457
477
|
searchInputRef={searchInputRef}
|
|
458
478
|
listboxRef={listboxRef}
|
|
459
479
|
filterQuery={deferredFilterQuery}
|
|
480
|
+
autocomplete={autocomplete}
|
|
481
|
+
name={name}
|
|
460
482
|
onFilterChange={setFilterQuery}
|
|
483
|
+
onAutocompleteSelect={(matchedValue) => {
|
|
484
|
+
onChange?.(matchedValue as M extends true ? T[] : T);
|
|
485
|
+
if (!multiple) {
|
|
486
|
+
setOpen(false);
|
|
487
|
+
}
|
|
488
|
+
}}
|
|
461
489
|
{...getListBoxLabelProps()}
|
|
462
490
|
/>
|
|
463
491
|
</OptionsOverlay>
|
|
@@ -565,6 +593,9 @@ interface SelectInputOptionsProps<T = string>
|
|
|
565
593
|
onFilterChange: (query: string) => void;
|
|
566
594
|
listBoxLabel?: string;
|
|
567
595
|
listBoxLabelledBy?: string;
|
|
596
|
+
autocomplete?: string;
|
|
597
|
+
name?: string;
|
|
598
|
+
onAutocompleteSelect?: (value: T) => void;
|
|
568
599
|
}
|
|
569
600
|
|
|
570
601
|
function SelectInputOptions<T = string>({
|
|
@@ -581,6 +612,9 @@ function SelectInputOptions<T = string>({
|
|
|
581
612
|
onFilterChange,
|
|
582
613
|
listBoxLabel,
|
|
583
614
|
listBoxLabelledBy,
|
|
615
|
+
autocomplete,
|
|
616
|
+
name,
|
|
617
|
+
onAutocompleteSelect,
|
|
584
618
|
}: SelectInputOptionsProps<T>) {
|
|
585
619
|
const intl = useIntl();
|
|
586
620
|
const virtualiserHandlerRef = useRef<VirtualizerHandle>(null);
|
|
@@ -664,6 +698,35 @@ function SelectInputOptions<T = string>({
|
|
|
664
698
|
);
|
|
665
699
|
};
|
|
666
700
|
|
|
701
|
+
const findMatchingItem = (autocompleteValue: string): T | null => {
|
|
702
|
+
const flatOptions = items
|
|
703
|
+
.flatMap((item) =>
|
|
704
|
+
item.type === 'group' ? item.options : item.type === 'option' ? [item] : [],
|
|
705
|
+
)
|
|
706
|
+
.filter(
|
|
707
|
+
(item): item is SelectInputOptionItem<NonNullable<T>> =>
|
|
708
|
+
item.type === 'option' && item.value != null,
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const exactMatch = flatOptions.find(
|
|
712
|
+
(option) =>
|
|
713
|
+
String(option.value) === autocompleteValue ||
|
|
714
|
+
option.filterMatchers?.some((matcher) => matcher === autocompleteValue),
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
if (exactMatch) {
|
|
718
|
+
return exactMatch.value;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const fuzzyMatch = flatOptions.find((option) =>
|
|
722
|
+
option.filterMatchers?.some((matcher) =>
|
|
723
|
+
matcher.toLowerCase().includes(autocompleteValue.toLowerCase()),
|
|
724
|
+
),
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
return fuzzyMatch ? fuzzyMatch.value : null;
|
|
728
|
+
};
|
|
729
|
+
|
|
667
730
|
return (
|
|
668
731
|
<ListboxBase.Options
|
|
669
732
|
as={SelectInputOptionsContainer}
|
|
@@ -684,6 +747,8 @@ function SelectInputOptions<T = string>({
|
|
|
684
747
|
<SearchInput
|
|
685
748
|
ref={searchInputRef}
|
|
686
749
|
id={id}
|
|
750
|
+
name={name}
|
|
751
|
+
autoComplete={autocomplete}
|
|
687
752
|
role="combobox"
|
|
688
753
|
shape="rectangle"
|
|
689
754
|
placeholder={filterPlaceholder}
|
|
@@ -703,8 +768,26 @@ function SelectInputOptions<T = string>({
|
|
|
703
768
|
onChange={(event) => {
|
|
704
769
|
// Free up resources and ensure not to go out of bounds when the
|
|
705
770
|
// resulting item count is less than before
|
|
771
|
+
const inputValue = event.currentTarget.value;
|
|
772
|
+
|
|
773
|
+
// Free up resources and ensure not to go out of bounds
|
|
706
774
|
setMountedIndexes([]);
|
|
707
|
-
onFilterChange(
|
|
775
|
+
onFilterChange(inputValue);
|
|
776
|
+
}}
|
|
777
|
+
onInput={(event) => {
|
|
778
|
+
const inputValue = event.currentTarget.value;
|
|
779
|
+
const inputElement = event.currentTarget;
|
|
780
|
+
|
|
781
|
+
if (autocomplete && onAutocompleteSelect && inputValue) {
|
|
782
|
+
setTimeout(() => {
|
|
783
|
+
if (inputElement.value === inputValue && inputValue.length > 2) {
|
|
784
|
+
const matchedValue = findMatchingItem(inputValue);
|
|
785
|
+
if (matchedValue !== null) {
|
|
786
|
+
onAutocompleteSelect(matchedValue);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}, 50);
|
|
790
|
+
}
|
|
708
791
|
}}
|
|
709
792
|
/>
|
|
710
793
|
</div>
|