superdesk-ui-framework 3.0.23 → 3.0.27

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.
@@ -1,5 +1,5 @@
1
1
  .sd-avatar {
2
- display: inline-block;
2
+ display: block;
3
3
  position: relative; // required for absolutely positioned indicators
4
4
 
5
5
  &.sd-avatar--indicator-status--offline {
@@ -339,3 +339,14 @@
339
339
  gap: $sd-base-increment * 1.5;
340
340
  }
341
341
  }
342
+
343
+ .avatar-popup {
344
+ background-color: var(--color-dropdown-menu-Bg);
345
+ border-radius: 5px;
346
+ padding: 1.5rem;
347
+ box-shadow: var(--sd-shadow__dropdown);
348
+ display: flex;
349
+ flex-direction: column;
350
+ gap: 4px;
351
+ overflow: auto;
352
+ }
@@ -1,7 +1,7 @@
1
1
  // CHECKBOX & RADIO BUTTONS : form-elements/checkbox.scss
2
2
  // --------------
3
3
 
4
- // Custom checkox & Radio buttons
4
+ // Custom checkox & Radio buttons
5
5
 
6
6
 
7
7
  $checkBoxBorderColor: var(--color-checkbox-border);
@@ -149,7 +149,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
149
149
  color: $white;
150
150
  }
151
151
  }
152
- .sd-label--disabled {
152
+ .sd-label--disabled {
153
153
  opacity: 1 !important;
154
154
  cursor: not-allowed !important;
155
155
  }
@@ -186,7 +186,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
186
186
  margin-inline-start: 0.8rem;
187
187
  }
188
188
  }
189
- .sd-label--disabled {
189
+ .sd-label--disabled {
190
190
  opacity: 0.40;
191
191
  }
192
192
  }
@@ -371,7 +371,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
371
371
  box-shadow: inset 0 2px 0 0 hsla(0, 0%, 0%, 0.2);
372
372
  }
373
373
  }
374
- .sd-label--disabled {
374
+ .sd-label--disabled {
375
375
  opacity: 1 !important;
376
376
  cursor: not-allowed !important;
377
377
  }
@@ -381,6 +381,8 @@ $checkButtonBorderRadius: $border-radius__base--small;
381
381
  .sd-check-button__group {
382
382
  display: flex;
383
383
  flex-direction: row;
384
+ flex-grow: 1;
385
+ flex-wrap: wrap;
384
386
  align-items: center;
385
387
  gap: $sd-base-increment * 1;
386
388
  flex: 0;
@@ -435,7 +437,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
435
437
  .sd-radio-new + label:empty {
436
438
  margin: 0 !important;
437
439
  }
438
- .sd-label--disabled {
440
+ .sd-label--disabled {
439
441
  opacity: 0.40;
440
442
  }
441
443
  &[label-position="left"], &[label-position="start"] {
@@ -609,7 +611,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
609
611
  box-shadow: inset 0 2px 0 0 rgba(0, 0, 0, 0.2), 0 0 0 3px $sd-colour--focus-shadow;
610
612
  }
611
613
  }
612
-
614
+
613
615
  }
614
616
  }
615
617
 
@@ -123,7 +123,7 @@ class PopupPositioner extends React.PureComponent<IPropsPopupPositioner> {
123
123
  ref={(el) => {
124
124
  this.wrapperEl = el;
125
125
  }}
126
- style={{zIndex: this.props.zIndex ?? 1, position: 'absolute', left: '-100vw'}}
126
+ style={{zIndex: this.props.zIndex ?? 1, position: 'absolute', left: '-100vw', display: 'flex'}}
127
127
  >
128
128
  {this.props.children}
129
129
  </div>
@@ -6,6 +6,7 @@ interface ITabs {
6
6
  ariaLabel?: string;
7
7
  children: Array<any>;
8
8
  onClick(i: number): void;
9
+ initiallySelectedIndex?: number;
9
10
  }
10
11
 
11
12
  interface ITabLabel {
@@ -24,14 +25,14 @@ interface ITabPanel {
24
25
  children: any;
25
26
  }
26
27
 
27
- export const TabLabel = ({ label }: ITabLabel) => {
28
+ export const TabLabel = ({label}: ITabLabel) => {
28
29
  return (
29
30
  <span>{label}</span>
30
31
  );
31
32
  };
32
33
 
33
- export const Tabs = ({ size, theme, ariaLabel, children, onClick }: ITabs) => {
34
- const [index, setIndex] = React.useState(0);
34
+ export const Tabs = ({initiallySelectedIndex, size, theme, ariaLabel, children, onClick}: ITabs) => {
35
+ const [index, setIndex] = React.useState(initiallySelectedIndex ?? 0);
35
36
 
36
37
  function handleSelected(i: number) {
37
38
  setIndex(i);
@@ -46,37 +47,47 @@ export const Tabs = ({ size, theme, ariaLabel, children, onClick }: ITabs) => {
46
47
  [`sd-nav-tabs--${size}`]: size && size !== undefined,
47
48
  'sd-nav-tabs--ui-dark': theme === 'dark',
48
49
  });
50
+
49
51
  return (
50
52
  <div className={classes} role='tablist' aria-label={ariaLabel ? ariaLabel : 'tabs'}>
51
- {children.map((item, i) =>
52
- <button
53
- key={i}
54
- aria-controls={'tabpanel-' + i}
55
- className={'sd-nav-tabs__tab' + (index === i ? ' sd-nav-tabs__tab--active' : '')}
56
- onClick={() => handleSelected(i)}
57
- role='tab'
58
- aria-selected={index === i ? 'true' : 'false'} >
59
- {item}
60
- </button>)
53
+ {
54
+ children.map((item, i) => (
55
+ <button
56
+ key={i}
57
+ aria-controls={'tabpanel-' + i}
58
+ className={'sd-nav-tabs__tab' + (index === i ? ' sd-nav-tabs__tab--active' : '')}
59
+ onClick={() => handleSelected(i)}
60
+ role='tab'
61
+ aria-selected={index === i ? 'true' : 'false'}
62
+ >
63
+ {item}
64
+ </button>
65
+ ))
61
66
  }
62
67
  </div>
63
68
  );
64
69
  };
65
70
 
66
- export const TabContent = ({ theme, children, activePanel }: ITabContent) => {
71
+ export const TabContent = ({theme, children, activePanel}: ITabContent) => {
67
72
  return (
68
- <div className={'sd-nav-tabs__content' +
69
- (theme === 'dark' ? ' sd-nav-tabs__content--ui-dark' : '')}>
70
- {children.map((panel: any, i: number) =>
71
- panel.props.indexValue === activePanel ?
72
- <div className='sd-nav-tabs__pane' role='tabpanel' aria-labelledby={'tab-' + activePanel} key={i}>
73
+ <div className={'sd-nav-tabs__content' + (theme === 'dark' ? ' sd-nav-tabs__content--ui-dark' : '')}>
74
+ {
75
+ children.map((panel: any, i: number) => panel.props.indexValue === activePanel && (
76
+ <div
77
+ className='sd-nav-tabs__pane'
78
+ role='tabpanel'
79
+ aria-labelledby={'tab-' + activePanel}
80
+ key={i}
81
+ >
73
82
  {panel}
74
- </div> : null)}
83
+ </div>
84
+ ))
85
+ }
75
86
  </div>
76
87
  );
77
88
  };
78
89
 
79
- export const TabPanel = ({ children, indexValue }: ITabPanel) => {
90
+ export const TabPanel = ({children, indexValue}: ITabPanel) => {
80
91
  return (
81
92
  <React.Fragment key={indexValue}>
82
93
  {children}
@@ -4,6 +4,8 @@ import {Avatar, IPropsAvatar} from './avatar';
4
4
  import {AvatarWrapper} from './avatar-wrapper';
5
5
  import {AvatarContentNumber} from './avatar-number';
6
6
  import {AvatarPlaceholder, IPropsAvatarPlaceholder} from './avatar-placeholder';
7
+ import { Spacer } from '../Spacer';
8
+ import {WithPopover} from '../WithPopover';
7
9
 
8
10
  export type IAvatarInGroup = Omit<IPropsAvatar, 'size'>;
9
11
  export type IAvatarPlaceholderInGroup = Omit<IPropsAvatarPlaceholder, 'size'>;
@@ -21,6 +23,13 @@ export interface IPropsAvatarGroup {
21
23
  * if exceeded, "+1"/"+2"/"+n" button will be shown
22
24
  */
23
25
  max?: number | 'show-all';
26
+
27
+ zIndex?: number;
28
+
29
+ // unless a custom onClick handler is passed
30
+ // a popover would get shown when maximum number
31
+ // of avatars is exceeded.
32
+ onClick?(): void;
24
33
  }
25
34
 
26
35
  function isAvatar(item: IAvatarInGroup | IAvatarPlaceholderInGroup): item is IAvatarInGroup {
@@ -46,41 +55,117 @@ export class AvatarGroup extends React.PureComponent<IPropsAvatarGroup> {
46
55
  })();
47
56
  const itemsOverLimit = items.length - max;
48
57
 
58
+ const PlusButtonWrapper: React.ComponentType<{onToggle(event: HTMLElement): void}> = ({children, onToggle}) => {
59
+ if (this.props.onClick == null) {
60
+ return (
61
+ <button
62
+ style={{padding: 0}}
63
+ onClick={(event) => {
64
+ if (this.props.onClick == null) {
65
+ onToggle(event.target as HTMLElement);
66
+ }
67
+ }}
68
+ >
69
+ {children}
70
+ </button>
71
+ );
72
+ } else {
73
+ return <>{children}</>;
74
+ }
75
+ };
76
+
77
+ const someHaveDisplayName = this.props.items.some((item) => isAvatar(item) && item.displayName.length > 0);
78
+
49
79
  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)) {
80
+ <WithPopover
81
+ zIndex={this.props.zIndex ?? 101}
82
+ placement='bottom-end'
83
+ component={() => (
84
+ <div className="avatar-popup">
85
+ {this.props.items.map((item, index) => {
61
86
  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
- />
87
+ someHaveDisplayName
88
+ ? <Spacer h alignItems='center' gap='16' noGrow key={index}>
89
+ {
90
+ isAvatar(item)
91
+ && item.displayName
92
+ }
93
+
94
+ {
95
+ isAvatar(item)
96
+ ? (
97
+ <Avatar
98
+ size='small'
99
+ imageUrl={item.imageUrl}
100
+ initials={item.initials}
101
+ displayName={item.displayName}
102
+ icon={item.icon}
103
+ />
104
+ )
105
+ : (
106
+ <AvatarPlaceholder
107
+ kind='plus-button'
108
+ size='small'
109
+ icon={item.icon}
110
+ onClick={item.onClick}
111
+ />
112
+ )
113
+ }
114
+ </Spacer>
115
+ : <div>
116
+ <AvatarPlaceholder
117
+ kind='plus-button'
118
+ size='small'
119
+ icon={item.icon}
120
+ onClick={isAvatar(item) ? undefined : item.onClick}
121
+ key={index}
122
+ />
123
+ </div>
71
124
  );
125
+ })}
126
+ </div>
127
+ )}
128
+ >
129
+ {(onToggle) => (
130
+ <div
131
+ className={classNames(
132
+ 'sd-avatar-group',
133
+ 'sd-avatar-group--stacked',
134
+ `sd-avatar-group--stacked--gap-${gap}`,
135
+ )}
136
+ role='group'
137
+ onClick={this.props.onClick}
138
+ >
139
+ {
140
+ items.slice(0, max).map((item, index) => {
141
+ if (isAvatar(item)) {
142
+ return (
143
+ <Avatar {...item} key={index} size={size} />
144
+ );
145
+ } else {
146
+ return (
147
+ <AvatarPlaceholder
148
+ {...item}
149
+ key={index}
150
+ size={this.props.size}
151
+ />
152
+ );
153
+ }
154
+ })
72
155
  }
73
- })
74
- }
75
156
 
76
- {
77
- itemsOverLimit > 0 && (
78
- <AvatarWrapper size={size} isEmpty={false}>
79
- <AvatarContentNumber number={`${itemsOverLimit}`} />
80
- </AvatarWrapper>
81
- )
82
- }
83
- </div>
157
+ {
158
+ itemsOverLimit > 0 && (
159
+ <PlusButtonWrapper onToggle={onToggle}>
160
+ <AvatarWrapper size={size} isEmpty={false}>
161
+ <AvatarContentNumber number={`${itemsOverLimit}`} />
162
+ </AvatarWrapper>
163
+ </PlusButtonWrapper>
164
+ )
165
+ }
166
+ </div>
167
+ )}
168
+ </WithPopover>
84
169
  );
85
170
  }
86
171
  }
@@ -5,7 +5,7 @@ import {AvatarContentText} from './avatar-text';
5
5
 
6
6
  export interface IPropsAvatar {
7
7
  imageUrl: string | null; // nullable, but mandatory to communicate importance
8
- tooltip: string | null; // nullable, but mandatory to communicate importance
8
+ displayName: string;
9
9
 
10
10
  /** 3 letters max */
11
11
  initials: string | null; // nullable, but mandatory to communicate importance
@@ -22,7 +22,7 @@ export interface IPropsAvatar {
22
22
 
23
23
  export class Avatar extends React.PureComponent<IPropsAvatar> {
24
24
  render() {
25
- const {imageUrl, initials, size, statusIndicator, administratorIndicator, icon, tooltip} = this.props;
25
+ const {imageUrl, initials, size, statusIndicator, administratorIndicator, icon, displayName} = this.props;
26
26
 
27
27
  return (
28
28
  <AvatarWrapper
@@ -35,10 +35,10 @@ export class Avatar extends React.PureComponent<IPropsAvatar> {
35
35
  {
36
36
  imageUrl != null || initials == null
37
37
  ? (
38
- <AvatarContentImage imageUrl={imageUrl} tooltipText={tooltip ?? undefined} />
38
+ <AvatarContentImage imageUrl={imageUrl} tooltipText={displayName} />
39
39
  )
40
40
  : (
41
- <AvatarContentText text={initials} tooltipText={tooltip ?? undefined} />
41
+ <AvatarContentText text={initials} tooltipText={displayName} />
42
42
  )
43
43
  }
44
44