superdesk-ui-framework 5.2.3 → 6.0.2
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/app/styles/_avatar.scss +147 -12
- package/app/styles/_tooltips.scss +7 -1
- package/app-typescript/components/CopyableTextBox.tsx +8 -1
- package/app-typescript/components/DatePicker.tsx +9 -2
- package/app-typescript/components/ShowPopup.tsx +1 -0
- package/app-typescript/components/avatar/avatar-group.tsx +25 -15
- package/app-typescript/components/avatar/avatar-with-inline-text-wrapper.tsx +22 -0
- package/app-typescript/components/avatar/avatar.tsx +119 -46
- package/dist/components/Avatar.tsx +523 -58
- package/dist/examples.bundle.css +6 -4
- package/dist/examples.bundle.js +29953 -29790
- package/dist/superdesk-ui.bundle.css +101 -18
- package/dist/superdesk-ui.bundle.js +29288 -29228
- package/dist/vendor.bundle.js +16 -16
- package/package.json +1 -1
- package/react/components/CopyableTextBox.d.ts +4 -1
- package/react/components/CopyableTextBox.js +4 -1
- package/react/components/DatePicker.js +9 -2
- package/react/components/ShowPopup.js +1 -1
- package/react/components/avatar/avatar-group.d.ts +4 -2
- package/react/components/avatar/avatar-group.js +7 -9
- package/react/components/avatar/avatar-with-inline-text-wrapper.d.ts +8 -0
- package/react/components/avatar/avatar-with-inline-text-wrapper.js +17 -0
- package/react/components/avatar/avatar.d.ts +28 -8
- package/react/components/avatar/avatar.js +67 -39
package/app/styles/_avatar.scss
CHANGED
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
|
|
93
93
|
.sd-avatar-content--text {
|
|
94
94
|
background-color: var(--sd-colour-avatar-bg);
|
|
95
|
+
cursor: default;
|
|
96
|
+
span {
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
.sd-avatar-content--dummy-img {
|
|
@@ -99,6 +103,7 @@
|
|
|
99
103
|
background-image: url('~../../images/avatar_dummy.svg');
|
|
100
104
|
background-repeat: no-repeat;
|
|
101
105
|
background-size: cover;
|
|
106
|
+
pointer-events: none;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
&.sd-avatar--empty-light {
|
|
@@ -121,6 +126,7 @@
|
|
|
121
126
|
inset-inline-end: -4px;
|
|
122
127
|
opacity: 1;
|
|
123
128
|
color: var(--color-text-subdued);
|
|
129
|
+
pointer-events: none;
|
|
124
130
|
[class^="icon-"], [class*=" icon-"] {
|
|
125
131
|
color: inherit;
|
|
126
132
|
text-shadow: -1px 1px 0 var(--sd-item__main-Bg), 1px 1px 0 var(--sd-item__main-Bg), 1px -1px 0 var(--sd-item__main-Bg), -1px -1px 0 var(--sd-item__main-Bg);
|
|
@@ -139,6 +145,7 @@
|
|
|
139
145
|
inset-block-end: -3px;
|
|
140
146
|
inset-inline-end: 3px;
|
|
141
147
|
box-shadow: 0 0 0 1px var(--sd-item__main-Bg);
|
|
148
|
+
pointer-events: none;
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
&.sd-avatar--x-small {
|
|
@@ -329,8 +336,9 @@
|
|
|
329
336
|
gap: 0;
|
|
330
337
|
margin-inline-end: 8px;
|
|
331
338
|
|
|
332
|
-
> .sd-avatar
|
|
333
|
-
|
|
339
|
+
> .sd-avatar,
|
|
340
|
+
> .d-contents .sd-avatar {
|
|
341
|
+
margin: 0 calc(-1 * var(--space--1)) 0 0;
|
|
334
342
|
.sd-avatar-content {
|
|
335
343
|
--avatar-shadow: var(--sd-item__main-Bg);
|
|
336
344
|
box-shadow: 0 0 0 2px var(--avatar-shadow);
|
|
@@ -340,29 +348,33 @@
|
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
350
|
|
|
343
|
-
> .sd-avatar--large
|
|
344
|
-
|
|
351
|
+
> .sd-avatar--large,
|
|
352
|
+
> .d-contents .sd-avatar--large {
|
|
353
|
+
margin: 0 calc(-1 * var(--space--1-5)) 0 0;
|
|
345
354
|
}
|
|
346
355
|
|
|
347
356
|
&.sd-avatar-group--stacked--gap-small {
|
|
348
|
-
gap:
|
|
349
|
-
> .sd-avatar
|
|
357
|
+
gap: var(--gap-0-5);
|
|
358
|
+
> .sd-avatar,
|
|
359
|
+
> .d-contents .sd-avatar {
|
|
350
360
|
margin: 0;
|
|
351
361
|
}
|
|
352
362
|
margin-inline-end: 0;
|
|
353
363
|
}
|
|
354
364
|
|
|
355
365
|
&.sd-avatar-group--stacked--gap-medium {
|
|
356
|
-
gap:
|
|
357
|
-
> .sd-avatar
|
|
366
|
+
gap: var(--gap-1);
|
|
367
|
+
> .sd-avatar,
|
|
368
|
+
> .d-contents .sd-avatar {
|
|
358
369
|
margin: 0;
|
|
359
370
|
}
|
|
360
371
|
margin-inline-end: 0;
|
|
361
372
|
}
|
|
362
373
|
|
|
363
374
|
&.sd-avatar-group--stacked--gap-large {
|
|
364
|
-
gap:
|
|
365
|
-
> .sd-avatar
|
|
375
|
+
gap: var(--gap-1-5);
|
|
376
|
+
> .sd-avatar,
|
|
377
|
+
> .d-contents .sd-avatar {
|
|
366
378
|
margin: 0;
|
|
367
379
|
}
|
|
368
380
|
margin-inline-end: 0;
|
|
@@ -371,14 +383,15 @@
|
|
|
371
383
|
&.sd-avatar-group--grid {
|
|
372
384
|
flex-wrap: wrap;
|
|
373
385
|
justify-content: flex-start;
|
|
374
|
-
gap:
|
|
386
|
+
gap: var(--gap-1-5);
|
|
375
387
|
}
|
|
376
388
|
}
|
|
377
389
|
|
|
378
390
|
.avatar-popup {
|
|
379
391
|
background-color: var(--color-dropdown-menu-Bg);
|
|
380
392
|
border-radius: var(--b-radius--large);
|
|
381
|
-
padding: var(--space--2);
|
|
393
|
+
padding-block: var(--space--2);
|
|
394
|
+
padding-inline-start: var(--space--1-5);
|
|
382
395
|
padding-inline-end: var(--space--3);
|
|
383
396
|
box-shadow: var(--sd-shadow__dropdown);
|
|
384
397
|
display: flex;
|
|
@@ -386,3 +399,125 @@
|
|
|
386
399
|
gap: var(--gap-1-5);
|
|
387
400
|
overflow: auto;
|
|
388
401
|
}
|
|
402
|
+
|
|
403
|
+
// Inline avatar display mode (like Material Design Chip)
|
|
404
|
+
.sd-avatar--inline {
|
|
405
|
+
display: inline-flex;
|
|
406
|
+
align-items: center;
|
|
407
|
+
gap: var(--gap-1);
|
|
408
|
+
vertical-align: middle;
|
|
409
|
+
background-color: var(--color-surface-faded);
|
|
410
|
+
border-radius: var(--b-radius--full);
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
.sd-avatar--inline__text {
|
|
414
|
+
color: var(--color-text);
|
|
415
|
+
font-size: var(--name-font-size);
|
|
416
|
+
line-height: 1;
|
|
417
|
+
white-space: nowrap;
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
text-overflow: ellipsis;
|
|
420
|
+
// max-width: 200px;
|
|
421
|
+
|
|
422
|
+
}
|
|
423
|
+
&:has(.sd-avatar--x-small) {
|
|
424
|
+
gap: var(--gap-0-5);
|
|
425
|
+
.sd-avatar--inline__text {
|
|
426
|
+
--name-font-size: var(--text-size-xx-small);
|
|
427
|
+
padding-inline-end: 1em;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
&:has(.sd-avatar--small) {
|
|
431
|
+
gap: var(--gap-0-5);
|
|
432
|
+
.sd-avatar--inline__text {
|
|
433
|
+
--name-font-size: var(--text-size-x-small);
|
|
434
|
+
padding-inline-end: 1em;
|
|
435
|
+
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
&:has(.sd-avatar--medium) {
|
|
439
|
+
gap: var(--gap-1);
|
|
440
|
+
.sd-avatar--inline__text {
|
|
441
|
+
--name-font-size: var(--text-size-small);
|
|
442
|
+
padding-inline-end: 1em;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
&:has(.sd-avatar--large) {
|
|
446
|
+
gap: var(--gap-1);
|
|
447
|
+
.sd-avatar--inline__text {
|
|
448
|
+
--name-font-size: var(--text-size-small);
|
|
449
|
+
padding-inline-end: 1.5em;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
&:has(.sd-avatar--x-large) {
|
|
453
|
+
gap: var(--gap-2);
|
|
454
|
+
.sd-avatar--inline__text {
|
|
455
|
+
--name-font-size: var(--text-size-medium);
|
|
456
|
+
padding-inline-end: 2.25em;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
&:has(.sd-avatar--xx-large) {
|
|
460
|
+
gap: var(--gap-3);
|
|
461
|
+
.sd-avatar--inline__text {
|
|
462
|
+
--name-font-size: var(--text-size-large);
|
|
463
|
+
padding-inline-end: 2.25em;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Modifier for text-start positioning (text before avatar)
|
|
468
|
+
&.sd-avatar--inline--text-start {
|
|
469
|
+
flex-direction: row-reverse;
|
|
470
|
+
|
|
471
|
+
.sd-avatar--inline__text {
|
|
472
|
+
padding-inline-end: 0;
|
|
473
|
+
padding-inline-start: 1em;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
&:has(.sd-avatar--x-small) {
|
|
477
|
+
.sd-avatar--inline__text {
|
|
478
|
+
padding-inline-start: 1em;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
&:has(.sd-avatar--small) {
|
|
482
|
+
.sd-avatar--inline__text {
|
|
483
|
+
padding-inline-start: 1em;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
&:has(.sd-avatar--medium) {
|
|
487
|
+
.sd-avatar--inline__text {
|
|
488
|
+
padding-inline-start: 1em;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
&:has(.sd-avatar--large) {
|
|
492
|
+
.sd-avatar--inline__text {
|
|
493
|
+
padding-inline-start: 1.5em;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
&:has(.sd-avatar--x-large) {
|
|
497
|
+
.sd-avatar--inline__text {
|
|
498
|
+
padding-inline-start: 2.25em;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
&:has(.sd-avatar--xx-large) {
|
|
502
|
+
.sd-avatar--inline__text {
|
|
503
|
+
padding-inline-start: 2.25em;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
&:not(.sd-avatar--inline--text-start) {
|
|
508
|
+
&:has(.sd-avatar--x-small) {
|
|
509
|
+
&:has(.sd-avatar__icon) {
|
|
510
|
+
.sd-avatar--inline__text {
|
|
511
|
+
padding-inline-start: 0.5em;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
&:has(.sd-avatar--small) {
|
|
516
|
+
&:has(.sd-avatar__icon) {
|
|
517
|
+
.sd-avatar--inline__text {
|
|
518
|
+
padding-inline-start: 0.25em;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
@@ -5,15 +5,18 @@ import {InputWrapper} from './Form';
|
|
|
5
5
|
import nextId from 'react-id-generator';
|
|
6
6
|
import {IInputCommon} from './Form/InputWrapper';
|
|
7
7
|
import {toasted} from './Toast';
|
|
8
|
+
import {Position} from './ToastMessage';
|
|
8
9
|
|
|
9
10
|
interface ICopyableTextBoxProps extends IInputCommon {
|
|
10
11
|
value: string;
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Defaults to medium
|
|
14
|
+
* Defaults to `medium`
|
|
14
15
|
*/
|
|
15
16
|
size?: 'medium' | 'large';
|
|
16
17
|
|
|
18
|
+
disableCopyButton?: boolean;
|
|
19
|
+
toastMessagePosition?: Position;
|
|
17
20
|
'data-test-id'?: string;
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -37,6 +40,7 @@ export const CopyableTextBox: React.FC<ICopyableTextBoxProps> = (props) => {
|
|
|
37
40
|
toasted.notify(gettext('Copied to clipboard'), {
|
|
38
41
|
type: 'success',
|
|
39
42
|
duration: 500,
|
|
43
|
+
position: props.toastMessagePosition,
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
setCopied(true);
|
|
@@ -52,6 +56,8 @@ export const CopyableTextBox: React.FC<ICopyableTextBoxProps> = (props) => {
|
|
|
52
56
|
.catch(() => {
|
|
53
57
|
toasted.notify(gettext("Couldn't copy"), {
|
|
54
58
|
type: 'warning',
|
|
59
|
+
duration: 500,
|
|
60
|
+
position: props.toastMessagePosition,
|
|
55
61
|
});
|
|
56
62
|
});
|
|
57
63
|
};
|
|
@@ -88,6 +94,7 @@ export const CopyableTextBox: React.FC<ICopyableTextBoxProps> = (props) => {
|
|
|
88
94
|
icon={copied ? 'ok' : 'copy'}
|
|
89
95
|
iconOnly={true}
|
|
90
96
|
onClick={handleCopy}
|
|
97
|
+
disabled={props.disableCopyButton}
|
|
91
98
|
type="default"
|
|
92
99
|
style="hollow"
|
|
93
100
|
size={props.size === 'medium' ? 'normal' : (props.size ?? 'normal')}
|
|
@@ -337,6 +337,13 @@ export class DatePickerISO extends React.PureComponent<IDatePickerISO> {
|
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
function getOptionLabel(options: Array<{id: string; label?: string}>, id: string | null | undefined): string {
|
|
341
|
+
if (id == null) {
|
|
342
|
+
return '';
|
|
343
|
+
}
|
|
344
|
+
return options.find((option) => option.id === id)?.label ?? id;
|
|
345
|
+
}
|
|
346
|
+
|
|
340
347
|
const MonthNavigator = ({
|
|
341
348
|
value,
|
|
342
349
|
options,
|
|
@@ -346,7 +353,7 @@ const MonthNavigator = ({
|
|
|
346
353
|
kind="synchronous"
|
|
347
354
|
value={[value]}
|
|
348
355
|
getOptions={() => options.map((option) => ({value: option.id}))}
|
|
349
|
-
getLabel={(
|
|
356
|
+
getLabel={(optionId) => getOptionLabel(options, optionId)}
|
|
350
357
|
getId={(option) => option}
|
|
351
358
|
onChange={(selected) => {
|
|
352
359
|
onChange(selected[0]);
|
|
@@ -355,7 +362,7 @@ const MonthNavigator = ({
|
|
|
355
362
|
labelHidden
|
|
356
363
|
valueTemplate={(item) => (
|
|
357
364
|
<div className="sd-datepicker__navigator-value sd-datepicker__navigator-month">
|
|
358
|
-
<div>{options
|
|
365
|
+
<div>{getOptionLabel(options, item)}</div>
|
|
359
366
|
<Icon name="chevron-down-thin" />
|
|
360
367
|
</div>
|
|
361
368
|
)}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import {Avatar, IPropsAvatar} from './avatar';
|
|
4
|
-
import {AvatarWrapper} from './avatar-wrapper';
|
|
3
|
+
import {Avatar, IAvatarWithTooltip, IPropsAvatar} from './avatar';
|
|
5
4
|
import {AvatarContentNumber} from './avatar-number';
|
|
6
5
|
import {AvatarPlaceholder, IPropsAvatarPlaceholder} from './avatar-placeholder';
|
|
7
|
-
import {Spacer} from '@sourcefabric/common';
|
|
8
6
|
import {WithPopover} from '../WithPopover';
|
|
9
7
|
|
|
10
|
-
export type IAvatarInGroup = Omit<IPropsAvatar, 'size'>;
|
|
8
|
+
export type IAvatarInGroup = {nameDisplay?: IAvatarWithTooltip} & Omit<IPropsAvatar, 'size' | 'nameDisplay'>;
|
|
11
9
|
export type IAvatarPlaceholderInGroup = Omit<IPropsAvatarPlaceholder, 'size'>;
|
|
12
10
|
|
|
13
11
|
export type IAvatarGroupItem = IAvatarInGroup | IAvatarPlaceholderInGroup;
|
|
@@ -81,32 +79,32 @@ export class AvatarGroup extends React.PureComponent<IPropsAvatarGroup> {
|
|
|
81
79
|
<div className="avatar-popup">
|
|
82
80
|
{this.props.items.map((item, index) => {
|
|
83
81
|
return someHaveDisplayName ? (
|
|
84
|
-
<
|
|
85
|
-
{isAvatar(item) && item.displayName}
|
|
86
|
-
|
|
82
|
+
<div className="d-flex items-center gap-1" key={index}>
|
|
87
83
|
{isAvatar(item) ? (
|
|
88
84
|
<Avatar
|
|
89
|
-
size="
|
|
85
|
+
size="medium"
|
|
90
86
|
imageUrl={item.imageUrl}
|
|
91
87
|
initials={item.initials}
|
|
92
88
|
displayName={item.displayName}
|
|
93
89
|
icon={item.icon}
|
|
94
90
|
statusDot={item.statusDot}
|
|
91
|
+
nameDisplay={{kind: 'none'}}
|
|
95
92
|
/>
|
|
96
93
|
) : (
|
|
97
94
|
<AvatarPlaceholder
|
|
98
95
|
kind="plus-button"
|
|
99
|
-
size="
|
|
96
|
+
size="medium"
|
|
100
97
|
icon={item.icon}
|
|
101
98
|
onClick={item.onClick}
|
|
102
99
|
/>
|
|
103
100
|
)}
|
|
104
|
-
|
|
101
|
+
{isAvatar(item) && item.displayName}
|
|
102
|
+
</div>
|
|
105
103
|
) : (
|
|
106
104
|
<div>
|
|
107
105
|
<AvatarPlaceholder
|
|
108
106
|
kind="plus-button"
|
|
109
|
-
size="
|
|
107
|
+
size="medium"
|
|
110
108
|
icon={item.icon}
|
|
111
109
|
onClick={isAvatar(item) ? undefined : item.onClick}
|
|
112
110
|
key={index}
|
|
@@ -129,7 +127,14 @@ export class AvatarGroup extends React.PureComponent<IPropsAvatarGroup> {
|
|
|
129
127
|
>
|
|
130
128
|
{items.slice(0, max).map((item, index) => {
|
|
131
129
|
if (isAvatar(item)) {
|
|
132
|
-
return
|
|
130
|
+
return (
|
|
131
|
+
<Avatar
|
|
132
|
+
{...item}
|
|
133
|
+
key={index}
|
|
134
|
+
size={size}
|
|
135
|
+
nameDisplay={item.nameDisplay ?? {kind: 'none'}}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
133
138
|
} else {
|
|
134
139
|
return <AvatarPlaceholder {...item} key={index} size={this.props.size} />;
|
|
135
140
|
}
|
|
@@ -137,9 +142,14 @@ export class AvatarGroup extends React.PureComponent<IPropsAvatarGroup> {
|
|
|
137
142
|
|
|
138
143
|
{itemsOverLimit > 0 && (
|
|
139
144
|
<PlusButtonWrapper onToggle={onToggle}>
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
<Avatar
|
|
146
|
+
size={size}
|
|
147
|
+
imageUrl={null}
|
|
148
|
+
displayName=""
|
|
149
|
+
initials={null}
|
|
150
|
+
customContent={<AvatarContentNumber number={`${itemsOverLimit}`} />}
|
|
151
|
+
nameDisplay={{kind: 'none'}}
|
|
152
|
+
/>
|
|
143
153
|
</PlusButtonWrapper>
|
|
144
154
|
)}
|
|
145
155
|
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type {IAvatarWithInlineText, IPropsAvatar} from './avatar';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
|
|
5
|
+
interface IProps {
|
|
6
|
+
placement: IAvatarWithInlineText['placement'];
|
|
7
|
+
displayName: IPropsAvatar['displayName'];
|
|
8
|
+
avatarElement: JSX.Element;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const AvatarWithInlineTextWrapper = ({placement, avatarElement, displayName}: IProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
className={classNames('sd-avatar--inline', {
|
|
15
|
+
'sd-avatar--inline--text-start': placement === 'start',
|
|
16
|
+
})}
|
|
17
|
+
>
|
|
18
|
+
{avatarElement}
|
|
19
|
+
<span className="sd-avatar--inline__text">{displayName}</span>
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
2
3
|
import {AvatarContentImage} from './avatar-image';
|
|
3
|
-
import {AvatarWrapper} from './avatar-wrapper';
|
|
4
4
|
import {AvatarContentText} from './avatar-text';
|
|
5
|
+
import {Icon} from '../Icon';
|
|
6
|
+
import {TooltipV2} from '../TooltipV2';
|
|
7
|
+
import {AvatarWithInlineTextWrapper} from './avatar-with-inline-text-wrapper';
|
|
8
|
+
import {assertNever} from '../../helpers';
|
|
9
|
+
|
|
10
|
+
export type IAvatarWithTooltip = {
|
|
11
|
+
kind: 'tooltip';
|
|
12
|
+
|
|
13
|
+
/** Defaults to `top` */
|
|
14
|
+
placement?: 'top' | 'left' | 'right' | 'bottom';
|
|
15
|
+
|
|
16
|
+
/** `displayName` used if not provided */
|
|
17
|
+
content?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type IAvatarWithInlineText = {
|
|
21
|
+
kind: 'inline';
|
|
22
|
+
|
|
23
|
+
/** Defaults to `end` */
|
|
24
|
+
placement?: 'start' | 'end';
|
|
25
|
+
};
|
|
5
26
|
|
|
6
27
|
export interface IPropsAvatar {
|
|
7
|
-
imageUrl: string | null;
|
|
8
|
-
displayName: string;
|
|
28
|
+
imageUrl: string | null;
|
|
9
29
|
|
|
10
30
|
/** 3 letters max */
|
|
11
|
-
initials: string | null;
|
|
31
|
+
initials: string | null;
|
|
12
32
|
|
|
33
|
+
displayName: string;
|
|
13
34
|
size: 'x-small' | 'small' | 'medium' | 'large' | 'x-large' | 'xx-large';
|
|
14
|
-
|
|
15
35
|
statusIndicator?: 'online' | 'offline';
|
|
16
36
|
administratorIndicator?: boolean;
|
|
17
37
|
icon?: {
|
|
@@ -21,13 +41,12 @@ export interface IPropsAvatar {
|
|
|
21
41
|
statusDot?: {
|
|
22
42
|
color?: string;
|
|
23
43
|
};
|
|
24
|
-
noAvatarPlaceholderColor?: 'subtle' | 'strong'; // defaults to strong; only applies to placeholder image
|
|
25
44
|
|
|
26
45
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
46
|
+
* Defaults to `strong`.
|
|
47
|
+
* Color scheme for placeholder when no image is available.
|
|
29
48
|
*/
|
|
30
|
-
|
|
49
|
+
noAvatarPlaceholderColor?: 'subtle' | 'strong';
|
|
31
50
|
|
|
32
51
|
/**
|
|
33
52
|
* JSX resulting from rendering of one of the following components:
|
|
@@ -35,46 +54,100 @@ export interface IPropsAvatar {
|
|
|
35
54
|
* AvatarContentImage
|
|
36
55
|
*/
|
|
37
56
|
customContent?: JSX.Element;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Defaults to `tooltip`.
|
|
60
|
+
*
|
|
61
|
+
* Controls how `tooltip` is displayed:
|
|
62
|
+
* - `tooltip`: Shows name in a tooltip
|
|
63
|
+
* - `none`: No name is displayed at all
|
|
64
|
+
* - `inline`: Shows name inline next to avatar (like Material Design Chip)
|
|
65
|
+
*/
|
|
66
|
+
nameDisplay?: IAvatarWithInlineText | IAvatarWithTooltip | {kind: 'none'};
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const tooltipCombined = [displayName, this.props.tooltip]
|
|
56
|
-
.filter((str) => (str ?? '').trim().length > 0)
|
|
57
|
-
.join('\n');
|
|
69
|
+
export const Avatar = React.memo<IPropsAvatar>((props) => {
|
|
70
|
+
const {
|
|
71
|
+
imageUrl,
|
|
72
|
+
initials,
|
|
73
|
+
size,
|
|
74
|
+
statusIndicator,
|
|
75
|
+
administratorIndicator,
|
|
76
|
+
icon,
|
|
77
|
+
noAvatarPlaceholderColor,
|
|
78
|
+
displayName,
|
|
79
|
+
customContent,
|
|
80
|
+
statusDot,
|
|
81
|
+
nameDisplay = {kind: 'tooltip'},
|
|
82
|
+
} = props;
|
|
58
83
|
|
|
84
|
+
const avatarContent = React.useMemo(() => {
|
|
85
|
+
if (customContent != null) {
|
|
86
|
+
return customContent;
|
|
87
|
+
} else if (imageUrl != null || initials == null) {
|
|
88
|
+
return <AvatarContentImage imageUrl={imageUrl} tooltipText={''} />;
|
|
89
|
+
} else {
|
|
90
|
+
return <AvatarContentText text={initials} tooltipText={''} />;
|
|
91
|
+
}
|
|
92
|
+
}, [customContent, imageUrl, initials]);
|
|
93
|
+
|
|
94
|
+
const avatarClassName = React.useMemo(
|
|
95
|
+
() =>
|
|
96
|
+
classNames('sd-avatar', {
|
|
97
|
+
'sd-avatar--x-small': size === 'x-small',
|
|
98
|
+
'sd-avatar--small': size === 'small',
|
|
99
|
+
'sd-avatar--medium': size === 'medium',
|
|
100
|
+
'sd-avatar--large': size === 'large',
|
|
101
|
+
'sd-avatar--x-large': size === 'x-large',
|
|
102
|
+
'sd-avatar--xx-large': size === 'xx-large',
|
|
103
|
+
'sd-avatar--indicator-status--online': statusIndicator === 'online',
|
|
104
|
+
'sd-avatar--indicator-status--offline': statusIndicator === 'offline',
|
|
105
|
+
'sd-avatar--empty-light': noAvatarPlaceholderColor === 'subtle',
|
|
106
|
+
}),
|
|
107
|
+
[size, statusIndicator, noAvatarPlaceholderColor],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const avatarElement = (
|
|
111
|
+
<span className={avatarClassName}>
|
|
112
|
+
{avatarContent}
|
|
113
|
+
|
|
114
|
+
{administratorIndicator === true && <i className="icon-settings sd-avatar--indicator-admin" />}
|
|
115
|
+
|
|
116
|
+
{icon != null && (
|
|
117
|
+
<span className="sd-avatar__icon">
|
|
118
|
+
<Icon name={icon.name} color={icon.color} />
|
|
119
|
+
</span>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{statusDot != null && (
|
|
123
|
+
<span style={{backgroundColor: statusDot.color}} className="sd-avatar__coverage-state" />
|
|
124
|
+
)}
|
|
125
|
+
</span>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const kind = nameDisplay.kind;
|
|
129
|
+
|
|
130
|
+
if (kind === 'inline') {
|
|
59
131
|
return (
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
statusDot={statusDot}
|
|
66
|
-
noAvatarPlaceholderColor={noAvatarPlaceholderColor}
|
|
67
|
-
>
|
|
68
|
-
{(() => {
|
|
69
|
-
if (customContent != null) {
|
|
70
|
-
return customContent;
|
|
71
|
-
} else if (imageUrl != null || initials == null) {
|
|
72
|
-
return <AvatarContentImage imageUrl={imageUrl} tooltipText={tooltipCombined} />;
|
|
73
|
-
} else {
|
|
74
|
-
return <AvatarContentText text={initials} tooltipText={tooltipCombined} />;
|
|
75
|
-
}
|
|
76
|
-
})()}
|
|
77
|
-
</AvatarWrapper>
|
|
132
|
+
<AvatarWithInlineTextWrapper
|
|
133
|
+
avatarElement={avatarElement}
|
|
134
|
+
displayName={displayName}
|
|
135
|
+
placement={nameDisplay.placement}
|
|
136
|
+
/>
|
|
78
137
|
);
|
|
138
|
+
} else if (kind === 'tooltip') {
|
|
139
|
+
return (
|
|
140
|
+
<TooltipV2 content={nameDisplay.content ?? displayName} placement={nameDisplay.placement}>
|
|
141
|
+
{({attributes}) => (
|
|
142
|
+
<span className="d-contents" {...attributes}>
|
|
143
|
+
{avatarElement}
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
</TooltipV2>
|
|
147
|
+
);
|
|
148
|
+
} else if (kind === 'none') {
|
|
149
|
+
return avatarElement;
|
|
150
|
+
} else {
|
|
151
|
+
assertNever(kind);
|
|
79
152
|
}
|
|
80
|
-
}
|
|
153
|
+
});
|