superdesk-ui-framework 3.0.18 → 3.0.20
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 +175 -25
- package/app/styles/design-tokens/_new-colors.scss +35 -2
- package/app-typescript/components/Icon.tsx +7 -1
- package/app-typescript/components/ShowPopup.tsx +173 -0
- package/app-typescript/components/Spacer.tsx +0 -8
- package/app-typescript/components/WithPopover.tsx +46 -0
- package/app-typescript/components/avatar/avatar-action-add.tsx +27 -0
- package/app-typescript/components/avatar/avatar-group.tsx +86 -0
- package/app-typescript/components/avatar/avatar-image.tsx +26 -0
- package/app-typescript/components/avatar/avatar-number.tsx +16 -0
- package/app-typescript/components/avatar/avatar-placeholder.tsx +35 -0
- package/app-typescript/components/avatar/avatar-text.tsx +19 -0
- package/app-typescript/components/avatar/avatar-wrapper.tsx +72 -0
- package/app-typescript/components/avatar/avatar.tsx +48 -0
- package/app-typescript/components/avatar/interfaces.ts +3 -0
- package/app-typescript/index.ts +8 -4
- package/dist/avatar_dummy.svg +4 -0
- package/dist/examples.bundle.js +3273 -2467
- package/dist/react/Avatar.tsx +628 -307
- package/dist/superdesk-ui.bundle.css +139 -27
- package/dist/superdesk-ui.bundle.js +2691 -1843
- package/dist/vendor.bundle.js +22 -22
- package/examples/pages/react/Avatar.tsx +628 -307
- package/images/avatar_dummy.svg +4 -0
- package/package.json +2 -1
- package/react/components/Icon.d.ts +1 -0
- package/react/components/Icon.js +1 -1
- package/react/components/ShowPopup.d.ts +10 -0
- package/react/components/ShowPopup.js +158 -0
- package/react/components/Spacer.d.ts +30 -0
- package/react/components/Spacer.js +86 -0
- package/react/components/WithPopover.d.ts +18 -0
- package/react/components/WithPopover.js +70 -0
- package/react/components/avatar/avatar-action-add.d.ts +9 -0
- package/react/components/avatar/avatar-action-add.js +59 -0
- package/react/components/avatar/avatar-group.d.ts +18 -0
- package/react/components/avatar/avatar-group.js +104 -0
- package/react/components/avatar/avatar-image.d.ts +9 -0
- package/react/components/avatar/avatar-image.js +62 -0
- package/react/components/avatar/avatar-number.d.ts +9 -0
- package/react/components/avatar/avatar-number.js +56 -0
- package/react/components/avatar/avatar-placeholder.d.ts +14 -0
- package/react/components/avatar/avatar-placeholder.js +57 -0
- package/react/components/avatar/avatar-text.d.ts +9 -0
- package/react/components/avatar/avatar-text.js +54 -0
- package/react/components/avatar/avatar-wrapper.d.ts +26 -0
- package/react/components/{Avatar.js → avatar/avatar-wrapper.js} +16 -57
- package/react/components/avatar/avatar.d.ts +17 -0
- package/react/components/avatar/avatar.js +59 -0
- package/react/components/avatar/interfaces.d.ts +3 -0
- package/react/components/avatar/interfaces.js +2 -0
- package/react/index.d.ts +8 -4
- package/react/index.js +20 -11
- package/app-typescript/components/Avatar.tsx +0 -122
- package/dist/avatar_64.png +0 -0
- package/react/components/Avatar.d.ts +0 -42
package/app/styles/_avatar.scss
CHANGED
@@ -2,25 +2,30 @@
|
|
2
2
|
display: inline-block;
|
3
3
|
position: relative; // required for absolutely positioned indicators
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
border: 1px solid #a2c59e;
|
17
|
-
box-sizing: border-box;
|
5
|
+
&.sd-avatar--indicator-status--offline {
|
6
|
+
.sd-avatar-content {
|
7
|
+
outline: 2px solid var(--sd-colour-avatar-outline--offline);
|
8
|
+
outline-offset: 2px;
|
9
|
+
}
|
10
|
+
&.sd-avatar--x-small,
|
11
|
+
&.sd-avatar--small {
|
12
|
+
.sd-avatar-content {
|
13
|
+
outline: 1px solid var(--sd-colour-avatar-outline--offline);
|
14
|
+
}
|
15
|
+
}
|
18
16
|
}
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
&.sd-avatar--indicator-status--online {
|
19
|
+
.sd-avatar-content {
|
20
|
+
outline: 2px solid var(--sd-colour-avatar-outline--online);
|
21
|
+
outline-offset: 2px;
|
22
|
+
}
|
23
|
+
&.sd-avatar--x-small,
|
24
|
+
&.sd-avatar--small {
|
25
|
+
.sd-avatar-content {
|
26
|
+
outline: 1px solid var(--sd-colour-avatar-outline--online);
|
27
|
+
}
|
28
|
+
}
|
24
29
|
}
|
25
30
|
|
26
31
|
&--indicator-admin {
|
@@ -46,6 +51,17 @@
|
|
46
51
|
}
|
47
52
|
}
|
48
53
|
|
54
|
+
&--x-small {
|
55
|
+
height: 20px;
|
56
|
+
width: 20px;
|
57
|
+
font-size: 1rem;
|
58
|
+
|
59
|
+
.sd-avatar--indicator-admin {
|
60
|
+
inset-block-start: -4px;
|
61
|
+
left: -6px;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
49
65
|
&--small {
|
50
66
|
height: 24px;
|
51
67
|
width: 24px;
|
@@ -60,7 +76,7 @@
|
|
60
76
|
&--medium {
|
61
77
|
height: 30px;
|
62
78
|
width: 30px;
|
63
|
-
font-size:
|
79
|
+
font-size: 1.4rem;
|
64
80
|
}
|
65
81
|
|
66
82
|
&--large {
|
@@ -147,25 +163,135 @@
|
|
147
163
|
text-transform: uppercase;
|
148
164
|
letter-spacing: -0.03em;
|
149
165
|
overflow: hidden;
|
150
|
-
|
166
|
+
|
151
167
|
> img {
|
152
168
|
width: 100%;
|
153
169
|
}
|
154
170
|
|
155
171
|
&--text {
|
156
|
-
background-color:
|
172
|
+
background-color: var(--sd-colour-avatar-bg);
|
157
173
|
}
|
158
|
-
|
159
|
-
&--
|
160
|
-
background-color:
|
161
|
-
background-image: url('~../../images/
|
174
|
+
|
175
|
+
&--dummy-img {
|
176
|
+
background-color: var(--sd-colour-avatar-bg);
|
177
|
+
background-image: url('~../../images/avatar_dummy.svg');
|
162
178
|
background-repeat: no-repeat;
|
163
179
|
background-size: cover;
|
164
|
-
|
180
|
+
border: 1px solid var(--sd-colour-avatar-border);
|
181
|
+
//box-shadow: inset 0 0 0 1px #005b7f;
|
182
|
+
}
|
183
|
+
&.sd-avatar-content--add-item {
|
184
|
+
background-color: var(--sd-colour-avatar-bg--light);
|
185
|
+
border: 1px dashed var(--sd-colour-avatar-border--light);
|
186
|
+
background-image: linear-gradient(var(--sd-colour-avatar-add) 0 0), linear-gradient(var(--sd-colour-avatar-add) 0 0);
|
187
|
+
background-position:center;
|
188
|
+
background-size: 50% 2px,2px 50%; /*thickness = 2px, length = 50% (25px)*/
|
189
|
+
background-repeat:no-repeat;
|
190
|
+
outline: 1px solid transparent;
|
191
|
+
outline-offset: 0px;
|
192
|
+
transition: all 0.2s ease;
|
193
|
+
}
|
194
|
+
|
195
|
+
&.sd-avatar-content--add-item--clickable {
|
196
|
+
&:hover {
|
197
|
+
background-image: linear-gradient(var(--sd-colour-avatar-add--hover) 0 0), linear-gradient(var(--sd-colour-avatar-add--hover) 0 0);
|
198
|
+
cursor: pointer;
|
199
|
+
outline: 1px solid var(--sd-colour-interactive);
|
200
|
+
outline-offset: 2px;
|
201
|
+
}
|
202
|
+
&:active {
|
203
|
+
background-image: linear-gradient(var(--sd-colour-avatar-add) 0 0), linear-gradient(var(--sd-colour-avatar-add) 0 0);
|
204
|
+
cursor: pointer;
|
205
|
+
outline: 2px solid var(--sd-colour-interactive);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
&.sd-avatar-content--number {
|
210
|
+
background-color: var(--sd-colour-avatar-bg--light);
|
211
|
+
color: var(--color-text-light);
|
212
|
+
outline: 1px solid transparent;
|
213
|
+
outline-offset: 0px;
|
214
|
+
transition: all 0.2s ease;
|
215
|
+
&:hover {
|
216
|
+
cursor: pointer;
|
217
|
+
outline: 1px solid var(--sd-colour-interactive);
|
218
|
+
outline-offset: 2px;
|
219
|
+
}
|
220
|
+
&:active {
|
221
|
+
cursor: pointer;
|
222
|
+
outline: 2px solid var(--sd-colour-interactive);
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
&.sd-avatar--empty-light {
|
228
|
+
.sd-avatar-content--dummy-img {
|
229
|
+
background-color: var(--sd-colour-avatar-bg--light);
|
230
|
+
border: 1px dashed var(--sd-colour-avatar-border--light);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
.sd-avatar__icon {
|
235
|
+
position: absolute;
|
236
|
+
display: flex;
|
237
|
+
align-items: center;
|
238
|
+
justify-content: center;
|
239
|
+
z-index: 1;
|
240
|
+
inset-block-end: -4px;
|
241
|
+
inset-inline-end: -4px;
|
242
|
+
[class^="icon-"], [class*=" icon-"] {
|
243
|
+
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);
|
244
|
+
}
|
245
|
+
|
246
|
+
}
|
247
|
+
&.sd-avatar--x-small {
|
248
|
+
.sd-avatar__icon {
|
249
|
+
inset-block-end: -4px;
|
250
|
+
inset-inline-end: -10px;
|
251
|
+
}
|
252
|
+
}
|
253
|
+
&.sd-avatar--small {
|
254
|
+
.sd-avatar__icon {
|
255
|
+
inset-block-end: -4px;
|
256
|
+
inset-inline-end: -8px;
|
257
|
+
}
|
258
|
+
}
|
259
|
+
&.sd-avatar--medium {
|
260
|
+
.sd-avatar__icon {
|
261
|
+
inset-block-end: -3px;
|
262
|
+
inset-inline-end: -6px;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
&.sd-avatar--large {
|
266
|
+
.sd-avatar__icon {
|
267
|
+
inset-block-end: -1px;
|
268
|
+
inset-inline-end: -4px;
|
269
|
+
}
|
270
|
+
}
|
271
|
+
&.sd-avatar--x-large {
|
272
|
+
.sd-avatar__icon {
|
273
|
+
inset-block-end: -2px;
|
274
|
+
inset-inline-end: -4px;
|
275
|
+
[class^="icon-"], [class*=" icon-"] {
|
276
|
+
--icon-base-size: 32px;
|
277
|
+
text-shadow: -2px 2px 0 var(--sd-item__main-Bg), 2px 2px 0 var(--sd-item__main-Bg), 2px -2px 0 var(--sd-item__main-Bg), -2px -2px 0 var(--sd-item__main-Bg);
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
&.sd-avatar--xx-large {
|
282
|
+
.sd-avatar__icon {
|
283
|
+
inset-block-end: -1px;
|
284
|
+
inset-inline-end: -2px;
|
285
|
+
[class^="icon-"], [class*=" icon-"] {
|
286
|
+
--icon-base-size: 32px;
|
287
|
+
text-shadow: -2px 2px 0 var(--sd-item__main-Bg), 2px 2px 0 var(--sd-item__main-Bg), 2px -2px 0 var(--sd-item__main-Bg), -2px -2px 0 var(--sd-item__main-Bg);
|
288
|
+
}
|
165
289
|
}
|
166
290
|
}
|
167
291
|
}
|
168
292
|
|
293
|
+
|
294
|
+
|
169
295
|
.sd-avatar-group {
|
170
296
|
display: flex;
|
171
297
|
&.sd-avatar-group--stacked {
|
@@ -180,8 +306,32 @@
|
|
180
306
|
&--large {
|
181
307
|
margin: 0 -1.2rem 0 0;
|
182
308
|
}
|
309
|
+
&:hover {
|
310
|
+
z-index: 100;
|
311
|
+
}
|
183
312
|
}
|
184
313
|
margin-inline-end: 8px;
|
314
|
+
&.sd-avatar-group--stacked--gap-small {
|
315
|
+
gap: $sd-base-increment * 0.5;
|
316
|
+
> .sd-avatar {
|
317
|
+
margin: 0;
|
318
|
+
}
|
319
|
+
margin-inline-end: 0;
|
320
|
+
}
|
321
|
+
&.sd-avatar-group--stacked--gap-medium {
|
322
|
+
gap: $sd-base-increment * 1;
|
323
|
+
> .sd-avatar {
|
324
|
+
margin: 0;
|
325
|
+
}
|
326
|
+
margin-inline-end: 0;
|
327
|
+
}
|
328
|
+
&.sd-avatar-group--stacked--gap-large {
|
329
|
+
gap: $sd-base-increment * 1.5;
|
330
|
+
> .sd-avatar {
|
331
|
+
margin: 0;
|
332
|
+
}
|
333
|
+
margin-inline-end: 0;
|
334
|
+
}
|
185
335
|
}
|
186
336
|
&.sd-avatar-group--grid {
|
187
337
|
flex-wrap: wrap;
|
@@ -350,6 +350,25 @@
|
|
350
350
|
|
351
351
|
--sd-colour__tag-label-Bg--inverse: var(--sd-colour-bg--08);
|
352
352
|
--sd-colour__tag-label-Txt--inverse: hsla(214, 13%, 98%, 1);
|
353
|
+
|
354
|
+
--sd-colour-avatar-bg: hsl(197, 100%, 25%);
|
355
|
+
--sd-colour-avatar-border: var(--sd-colour-avatar-bg);
|
356
|
+
|
357
|
+
--sd-colour-avatar-bg--light: hsla(214, 13%, 90%, 1);
|
358
|
+
--sd-colour-avatar-border--light: hsla(214, 13%, 65%, 1);
|
359
|
+
--sd-colour-avatar-border--light-hover: hsla(214, 13%, 45%, 1);
|
360
|
+
--sd-colour-avatar-dummy: hsla(214, 13%, 75%, 0.60);
|
361
|
+
--sd-colour-avatar-add: hsla(214, 13%, 55%, 1);
|
362
|
+
--sd-colour-avatar-add--hover: hsla(214, 13%, 45%, 1);
|
363
|
+
|
364
|
+
--sd-colour-avatar-outline--offline: hsla(214, 13%, 85%, 1);
|
365
|
+
--sd-colour-avatar-outline--online: var(--sd-colour-success);
|
366
|
+
|
367
|
+
--sd-colour-state--default: var(--color-text-lighter);
|
368
|
+
--sd-colour-state--in-progress: var(--sd-colour-highlight);
|
369
|
+
--sd-colour-state--done: var(--sd-colour-success);
|
370
|
+
|
371
|
+
|
353
372
|
}
|
354
373
|
|
355
374
|
|
@@ -495,6 +514,20 @@
|
|
495
514
|
|
496
515
|
--sd-colour__tag-label-Bg--inverse: hsla(214, 13%, 55%, 0.95);
|
497
516
|
--sd-colour__tag-label-Txt--inverse: hsla(214, 13%, 10%, 1);
|
517
|
+
|
518
|
+
--sd-colour-avatar-bg: hsl(197, 100%, 25%);
|
519
|
+
--sd-colour-avatar-border: hsl(197, 100%, 25%);
|
520
|
+
|
521
|
+
--sd-colour-avatar-bg--light: hsla(214, 13%, 20%, 1);
|
522
|
+
--sd-colour-avatar-border--light: hsla(214, 13%, 45%, 1);
|
523
|
+
--sd-colour-avatar-border--light-hover: hsla(214, 13%, 65%, 1);
|
524
|
+
--sd-colour-avatar-dummy: hsla(214, 13%, 98%, 0.25);
|
525
|
+
--sd-colour-avatar-add: hsla(214, 13%, 65%, 1);
|
526
|
+
--sd-colour-avatar-add--hover: hsla(214, 13%, 90%, 1);
|
527
|
+
|
528
|
+
--sd-colour-state--default: var(--color-text-lighter);
|
529
|
+
--sd-colour-state--in-progress: var(--sd-colour-highlight);
|
530
|
+
--sd-colour-state--done: var(--sd-colour-success);
|
498
531
|
}
|
499
532
|
|
500
533
|
|
@@ -629,6 +662,7 @@
|
|
629
662
|
--color-navbutton-bg-dark-hover: hsla(214, 13%, 60%, 1);
|
630
663
|
|
631
664
|
--color-navbutton-shadow-active: hsla(214, 13%, 5%, 0.12);
|
665
|
+
|
632
666
|
}
|
633
667
|
|
634
668
|
// CSS variables for dark theme
|
@@ -711,8 +745,7 @@
|
|
711
745
|
[class*=" big-icon--"] {
|
712
746
|
color: var(--color-text);
|
713
747
|
}
|
714
|
-
|
715
|
-
}
|
748
|
+
}
|
716
749
|
|
717
750
|
/// ======= END from _colors.scss ======== ///////////
|
718
751
|
/// ====================================== ///////////
|
@@ -7,6 +7,7 @@ interface IProps {
|
|
7
7
|
className?: string;
|
8
8
|
scale?: '2x' | '3x' | '4x';
|
9
9
|
ariaHidden?: boolean;
|
10
|
+
color?: string;
|
10
11
|
}
|
11
12
|
|
12
13
|
export class Icon extends React.PureComponent<IProps> {
|
@@ -19,7 +20,12 @@ export class Icon extends React.PureComponent<IProps> {
|
|
19
20
|
[`scale--${this.props.scale}`]: this.props.scale,
|
20
21
|
});
|
21
22
|
return (
|
22
|
-
<i
|
23
|
+
<i
|
24
|
+
className={classes}
|
25
|
+
aria-label={this.props.name}
|
26
|
+
aria-hidden={this.props.ariaHidden}
|
27
|
+
style={{color: this.props.color}}
|
28
|
+
/>
|
23
29
|
);
|
24
30
|
}
|
25
31
|
}
|
@@ -0,0 +1,173 @@
|
|
1
|
+
|
2
|
+
import * as React from 'react';
|
3
|
+
import ReactDOM from 'react-dom';
|
4
|
+
import {createPopper, Instance as PopperInstance, Placement, Modifier} from '@popperjs/core';
|
5
|
+
import {throttle} from 'lodash';
|
6
|
+
import maxSize from 'popper-max-size-modifier';
|
7
|
+
|
8
|
+
interface IPropsPopupPositioner {
|
9
|
+
referenceElement: HTMLElement;
|
10
|
+
placement: Placement;
|
11
|
+
zIndex?: number;
|
12
|
+
onClose(): void;
|
13
|
+
closeOnHoverEnd?: boolean;
|
14
|
+
}
|
15
|
+
|
16
|
+
class PopupPositioner extends React.PureComponent<IPropsPopupPositioner> {
|
17
|
+
private wrapperEl: HTMLDivElement | null;
|
18
|
+
private popper: PopperInstance | null;
|
19
|
+
|
20
|
+
constructor(props: IPropsPopupPositioner) {
|
21
|
+
super(props);
|
22
|
+
|
23
|
+
this.closeOnClick = this.closeOnClick.bind(this);
|
24
|
+
this.closeOnScroll = throttle(this.closeOnScroll.bind(this), 200);
|
25
|
+
this.closeOnMouseLeave = this.closeOnMouseLeave.bind(this);
|
26
|
+
this.wrapperEl = null;
|
27
|
+
this.popper = null;
|
28
|
+
}
|
29
|
+
|
30
|
+
closeOnClick(event: MouseEvent) {
|
31
|
+
if (this.wrapperEl == null) {
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
if (
|
36
|
+
this.props.referenceElement.contains(event.target as Node) !== true
|
37
|
+
&& this.wrapperEl.contains(event.target as Node) !== true
|
38
|
+
) {
|
39
|
+
this.props.onClose();
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
closeOnScroll(event: Event) {
|
44
|
+
if (this.wrapperEl == null) {
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
|
48
|
+
if (this.wrapperEl.contains(event.target as Node) !== true) {
|
49
|
+
this.props.onClose();
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
closeOnMouseLeave(event: MouseEvent) {
|
54
|
+
if (this.wrapperEl == null) {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
|
58
|
+
if (this.wrapperEl.contains(event.target as Node) !== true) {
|
59
|
+
this.props.onClose();
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
componentDidMount() {
|
64
|
+
window.addEventListener('click', this.closeOnClick, {capture: true});
|
65
|
+
window.addEventListener('scroll', this.closeOnScroll, true);
|
66
|
+
|
67
|
+
if (this.props.closeOnHoverEnd && this.wrapperEl != null) {
|
68
|
+
this.props.referenceElement.addEventListener('mouseleave', this.closeOnMouseLeave);
|
69
|
+
this.wrapperEl.addEventListener('mouseleave', this.closeOnMouseLeave);
|
70
|
+
}
|
71
|
+
|
72
|
+
const applyMaxSize: Modifier<any, any> = {
|
73
|
+
name: 'applyMaxSize',
|
74
|
+
enabled: true,
|
75
|
+
phase: 'beforeWrite',
|
76
|
+
requires: ['maxSize'],
|
77
|
+
fn: ({state}) => {
|
78
|
+
const {height} = state.modifiersData.maxSize;
|
79
|
+
|
80
|
+
// subtracting 10 in order to make a gap between the edge of the viewport
|
81
|
+
state.styles.popper.maxHeight = `${height - 10}px`;
|
82
|
+
},
|
83
|
+
};
|
84
|
+
|
85
|
+
if (this.wrapperEl != null) {
|
86
|
+
/**
|
87
|
+
* Wait until referenceElement renders so createPopper
|
88
|
+
* can take its dimensions into account.
|
89
|
+
*/
|
90
|
+
setTimeout(() => {
|
91
|
+
if (this.wrapperEl != null) {
|
92
|
+
this.popper = createPopper(
|
93
|
+
this.props.referenceElement,
|
94
|
+
this.wrapperEl,
|
95
|
+
{
|
96
|
+
placement: this.props.placement,
|
97
|
+
modifiers: [
|
98
|
+
maxSize,
|
99
|
+
applyMaxSize,
|
100
|
+
],
|
101
|
+
},
|
102
|
+
);
|
103
|
+
}
|
104
|
+
}, 50);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
componentWillUnmount() {
|
109
|
+
window.removeEventListener('click', this.closeOnClick);
|
110
|
+
window.removeEventListener('scroll', this.closeOnScroll, true);
|
111
|
+
|
112
|
+
if (this.props.closeOnHoverEnd && this.wrapperEl != null) {
|
113
|
+
this.props.referenceElement.removeEventListener('mouseleave', this.closeOnMouseLeave);
|
114
|
+
this.wrapperEl.removeEventListener('mouseleave', this.closeOnMouseLeave);
|
115
|
+
}
|
116
|
+
|
117
|
+
this.popper?.destroy?.();
|
118
|
+
}
|
119
|
+
|
120
|
+
render() {
|
121
|
+
return (
|
122
|
+
<div
|
123
|
+
ref={(el) => {
|
124
|
+
this.wrapperEl = el;
|
125
|
+
}}
|
126
|
+
style={{zIndex: this.props.zIndex ?? 1, position: 'absolute', left: '-100vw'}}
|
127
|
+
>
|
128
|
+
{this.props.children}
|
129
|
+
</div>
|
130
|
+
);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
/**
|
135
|
+
* The popup will remove itself if click/scroll events are detected outside the popup.
|
136
|
+
*/
|
137
|
+
export function showPopup(
|
138
|
+
referenceElement: HTMLElement,
|
139
|
+
placement: Placement,
|
140
|
+
Component: React.ComponentType<{closePopup(): void}>,
|
141
|
+
zIndex?: number,
|
142
|
+
closeOnHoverEnd?: boolean,
|
143
|
+
onClose?: () => void,
|
144
|
+
): {close: () => void} {
|
145
|
+
const el = document.createElement('div');
|
146
|
+
|
147
|
+
document.body.appendChild(el);
|
148
|
+
|
149
|
+
const closeFn = () => {
|
150
|
+
ReactDOM.unmountComponentAtNode(el);
|
151
|
+
el.remove();
|
152
|
+
onClose?.();
|
153
|
+
};
|
154
|
+
|
155
|
+
ReactDOM.render(
|
156
|
+
(
|
157
|
+
<PopupPositioner
|
158
|
+
referenceElement={referenceElement}
|
159
|
+
placement={placement}
|
160
|
+
onClose={closeFn}
|
161
|
+
zIndex={zIndex}
|
162
|
+
closeOnHoverEnd={closeOnHoverEnd || false}
|
163
|
+
>
|
164
|
+
<Component
|
165
|
+
closePopup={closeFn}
|
166
|
+
/>
|
167
|
+
</PopupPositioner>
|
168
|
+
),
|
169
|
+
el,
|
170
|
+
);
|
171
|
+
|
172
|
+
return {close: closeFn};
|
173
|
+
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import * as React from 'react';
|
2
|
-
import classNames from 'classnames';
|
3
2
|
|
4
3
|
export interface IPropsSpacer {
|
5
4
|
h?: boolean; // horizontal
|
@@ -21,13 +20,6 @@ export interface IPropsSpacer {
|
|
21
20
|
}
|
22
21
|
|
23
22
|
export class Spacer extends React.PureComponent<IPropsSpacer> {
|
24
|
-
constructor(props: IPropsSpacer) {
|
25
|
-
super(props);
|
26
|
-
this.state = {
|
27
|
-
items: [],
|
28
|
-
};
|
29
|
-
|
30
|
-
}
|
31
23
|
render() {
|
32
24
|
const {h, v, gap, justifyContent, alignItems, noGrow, noWrap} = this.props;
|
33
25
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
import * as React from 'react';
|
3
|
+
import {Placement} from '@popperjs/core';
|
4
|
+
import {showPopup} from './ShowPopup';
|
5
|
+
|
6
|
+
export interface IPropsWithPopover {
|
7
|
+
children(toggle: (referenceElement: HTMLElement) => void): React.ReactNode;
|
8
|
+
placement: Placement;
|
9
|
+
component: React.ComponentType<{closePopup(): void}>;
|
10
|
+
zIndex?: number;
|
11
|
+
closeOnHoverEnd?: boolean;
|
12
|
+
onClose?: () => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
export class WithPopover extends React.PureComponent<IPropsWithPopover> {
|
16
|
+
private closePopup?: () => void;
|
17
|
+
|
18
|
+
constructor(props: IPropsWithPopover) {
|
19
|
+
super(props);
|
20
|
+
|
21
|
+
this.togglePopup = this.togglePopup.bind(this);
|
22
|
+
}
|
23
|
+
|
24
|
+
togglePopup(referenceElement: HTMLElement) {
|
25
|
+
if (this.closePopup != null) {
|
26
|
+
this.closePopup();
|
27
|
+
this.closePopup = undefined;
|
28
|
+
} else {
|
29
|
+
this.closePopup = showPopup(
|
30
|
+
referenceElement,
|
31
|
+
this.props.placement,
|
32
|
+
this.props.component,
|
33
|
+
this.props.zIndex,
|
34
|
+
this.props.closeOnHoverEnd,
|
35
|
+
() => {
|
36
|
+
this.closePopup = undefined;
|
37
|
+
this.props.onClose?.();
|
38
|
+
},
|
39
|
+
).close;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
render(): React.ReactNode {
|
44
|
+
return this.props.children(this.togglePopup);
|
45
|
+
}
|
46
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import {IPropsBase} from './interfaces';
|
3
|
+
|
4
|
+
interface IProps extends IPropsBase {
|
5
|
+
onClick?(): void;
|
6
|
+
}
|
7
|
+
|
8
|
+
export class AvatarContentAdd extends React.PureComponent<IProps> {
|
9
|
+
render() {
|
10
|
+
if (this.props.onClick == null) {
|
11
|
+
return (
|
12
|
+
<span
|
13
|
+
className="sd-avatar-content sd-avatar-content--add-item"
|
14
|
+
title={this.props.tooltipText}
|
15
|
+
/>
|
16
|
+
);
|
17
|
+
} else {
|
18
|
+
return (
|
19
|
+
<button
|
20
|
+
className="sd-avatar-content sd-avatar-content--add-item sd-avatar-content--add-item--clickable"
|
21
|
+
title={this.props.tooltipText}
|
22
|
+
onClick={() => this.props.onClick?.()}
|
23
|
+
/>
|
24
|
+
);
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import classNames from 'classnames';
|
3
|
+
import {Avatar, IPropsAvatar} from './avatar';
|
4
|
+
import {AvatarWrapper} from './avatar-wrapper';
|
5
|
+
import {AvatarContentNumber} from './avatar-number';
|
6
|
+
import {AvatarPlaceholder, IPropsAvatarPlaceholder} from './avatar-placeholder';
|
7
|
+
|
8
|
+
export type IAvatarInGroup = Omit<IPropsAvatar, 'size'>;
|
9
|
+
export type IAvatarPlaceholderInGroup = Omit<IPropsAvatarPlaceholder, 'size'>;
|
10
|
+
|
11
|
+
export type IAvatarGroupItem = IAvatarInGroup | IAvatarPlaceholderInGroup;
|
12
|
+
|
13
|
+
type IGap = 'none' | 'small'| 'medium'| 'large';
|
14
|
+
|
15
|
+
export interface IPropsAvatarGroup {
|
16
|
+
size: IPropsAvatar['size'];
|
17
|
+
items: Array<IAvatarGroupItem>;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* maximum number of avatars to shown inline; defaults to 4
|
21
|
+
* if exceeded, "+1"/"+2"/"+n" button will be shown
|
22
|
+
*/
|
23
|
+
max?: number | 'show-all';
|
24
|
+
}
|
25
|
+
|
26
|
+
function isAvatar(item: IAvatarInGroup | IAvatarPlaceholderInGroup): item is IAvatarInGroup {
|
27
|
+
return (item as any)['kind'] == null;
|
28
|
+
}
|
29
|
+
|
30
|
+
export class AvatarGroup extends React.PureComponent<IPropsAvatarGroup> {
|
31
|
+
render() {
|
32
|
+
const {size, items} = this.props;
|
33
|
+
const someIconsHaveExtraElements = items.filter(isAvatar).some(
|
34
|
+
({icon, administratorIndicator}) => icon != null || administratorIndicator != null,
|
35
|
+
);
|
36
|
+
const gap: IGap = someIconsHaveExtraElements ? 'medium' : 'none';
|
37
|
+
|
38
|
+
const max: number = (() => {
|
39
|
+
if (this.props.max === 'show-all') {
|
40
|
+
return this.props.items.length;
|
41
|
+
} else if (this.props.max == null) {
|
42
|
+
return 4;
|
43
|
+
} else {
|
44
|
+
return this.props.max;
|
45
|
+
}
|
46
|
+
})();
|
47
|
+
const itemsOverLimit = items.length - max;
|
48
|
+
|
49
|
+
return (
|
50
|
+
<div
|
51
|
+
className={classNames(
|
52
|
+
'sd-avatar-group',
|
53
|
+
'sd-avatar-group--stacked',
|
54
|
+
`sd-avatar-group--stacked--gap-${gap}`,
|
55
|
+
)}
|
56
|
+
role='group'
|
57
|
+
>
|
58
|
+
{
|
59
|
+
items.slice(0, max).map((item, index) => {
|
60
|
+
if (isAvatar(item)) {
|
61
|
+
return (
|
62
|
+
<Avatar {...item} key={index} size={size} />
|
63
|
+
);
|
64
|
+
} else {
|
65
|
+
return (
|
66
|
+
<AvatarPlaceholder
|
67
|
+
{...item}
|
68
|
+
key={index}
|
69
|
+
size={this.props.size}
|
70
|
+
/>
|
71
|
+
);
|
72
|
+
}
|
73
|
+
})
|
74
|
+
}
|
75
|
+
|
76
|
+
{
|
77
|
+
itemsOverLimit > 0 && (
|
78
|
+
<AvatarWrapper size={size} isEmpty={false}>
|
79
|
+
<AvatarContentNumber number={`${itemsOverLimit}`} />
|
80
|
+
</AvatarWrapper>
|
81
|
+
)
|
82
|
+
}
|
83
|
+
</div>
|
84
|
+
);
|
85
|
+
}
|
86
|
+
}
|