superdesk-ui-framework 4.0.13 → 4.0.16

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 (31) hide show
  1. package/.github/workflows/publish-to-npm.yml +1 -0
  2. package/app/styles/app.scss +1 -0
  3. package/app-typescript/components/CreateButton.tsx +1 -1
  4. package/app-typescript/components/DateTimePicker.tsx +33 -29
  5. package/app-typescript/components/Editor/EditorButton.tsx +1 -1
  6. package/app-typescript/components/IconButton.tsx +1 -3
  7. package/app-typescript/components/Lists/TableList.tsx +4 -4
  8. package/app-typescript/components/Switch.tsx +0 -1
  9. package/app-typescript/components/Tooltip.tsx +76 -48
  10. package/app-typescript/components/TreeSelect/TreeSelect.tsx +14 -1
  11. package/dist/components/Tooltips.tsx +1 -44
  12. package/dist/components/TreeSelect.tsx +10 -15
  13. package/dist/examples.bundle.js +3588 -1104
  14. package/dist/playgrounds/react-playgrounds/RundownEditor.tsx +1 -1
  15. package/dist/playgrounds/react-playgrounds/TestGround.tsx +26 -26
  16. package/dist/superdesk-ui.bundle.css +1 -1
  17. package/dist/superdesk-ui.bundle.js +3545 -1041
  18. package/examples/pages/components/Tooltips.tsx +1 -44
  19. package/examples/pages/components/TreeSelect.tsx +10 -15
  20. package/examples/pages/playgrounds/react-playgrounds/RundownEditor.tsx +1 -1
  21. package/examples/pages/playgrounds/react-playgrounds/TestGround.tsx +26 -26
  22. package/package.json +3 -2
  23. package/react/components/CreateButton.js +1 -1
  24. package/react/components/DateTimePicker.js +9 -7
  25. package/react/components/IconButton.js +1 -1
  26. package/react/components/Lists/TableList.js +4 -4
  27. package/react/components/Switch.js +1 -1
  28. package/react/components/Tooltip.d.ts +10 -4
  29. package/react/components/Tooltip.js +64 -64
  30. package/react/components/TreeSelect/TreeSelect.d.ts +1 -0
  31. package/react/components/TreeSelect/TreeSelect.js +8 -1
@@ -4,6 +4,7 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - 'develop'
7
+ - 'v3'
7
8
 
8
9
  jobs:
9
10
  publish:
@@ -108,6 +108,7 @@
108
108
  // Prime React
109
109
  @import '../../node_modules/@superdesk/primereact/resources/primereact.min.css';
110
110
  @import '../../node_modules/primeicons/primeicons.css';
111
+ @import '~tippy.js/dist/tippy.css';
111
112
  @import 'pr-superdesk-theme';
112
113
  @import 'primereact/pr-dialog';
113
114
  @import 'primereact/pr-menu';
@@ -22,7 +22,7 @@ export class CreateButton extends React.PureComponent<IProps> {
22
22
  });
23
23
  const value = this.props.value === undefined ? 'button' : this.props.value;
24
24
  return (
25
- <Tooltip text={this.props.ariaValue} flow={this.props.toolTipFlow} appendToBody={true}>
25
+ <Tooltip text={this.props.ariaValue} flow={this.props.toolTipFlow}>
26
26
  <button type={value}
27
27
  className={classes}
28
28
  tabIndex={0}
@@ -59,35 +59,39 @@ export class DateTimePicker extends React.PureComponent<IProps> {
59
59
 
60
60
  return (
61
61
  <div style={{width: this.props.width ? this.props.width : MIN_WIDTH}}>
62
- <Spacer h gap="8" alignItems='end'>
63
- <DatePicker
64
- disabled={this.props.disabled}
65
- preview={this.props.preview}
66
- required={this.props.required}
67
- hideClearButton={true}
68
- value={this.props.value}
69
- onChange={(val) => {
70
- this.handleDateChange(val);
71
- }}
72
- dateFormat={this.props.dateFormat}
73
- label={this.props.label.text}
74
- inlineLabel={this.props.label.hidden ?? false}
75
- labelHidden={this.props.label.hidden ?? false}
76
- fullWidth={this.props.fullWidth}
77
- />
78
- <TimePicker
79
- disabled={this.props.disabled}
80
- preview={this.props.preview}
81
- value={convertedTimeValue}
82
- onChange={(val) => {
83
- this.handleTimeChange(val);
84
- }}
85
- inlineLabel
86
- labelHidden
87
- allowSeconds={this.props.allowSeconds}
88
- fullWidth={this.props.fullWidth}
89
- required={this.props.required}
90
- />
62
+ <Spacer h gap="8" alignItems='end' noWrap>
63
+ <div style={{flexGrow: 1}}>
64
+ <DatePicker
65
+ disabled={this.props.disabled}
66
+ preview={this.props.preview}
67
+ required={this.props.required}
68
+ hideClearButton={true}
69
+ value={this.props.value}
70
+ onChange={(val) => {
71
+ this.handleDateChange(val);
72
+ }}
73
+ dateFormat={this.props.dateFormat}
74
+ label={this.props.label.text}
75
+ inlineLabel={this.props.label.hidden ?? false}
76
+ labelHidden={this.props.label.hidden ?? false}
77
+ fullWidth={this.props.fullWidth}
78
+ />
79
+ </div>
80
+ <div style={{flexGrow: 1}}>
81
+ <TimePicker
82
+ disabled={this.props.disabled}
83
+ preview={this.props.preview}
84
+ value={convertedTimeValue}
85
+ onChange={(val) => {
86
+ this.handleTimeChange(val);
87
+ }}
88
+ inlineLabel
89
+ labelHidden
90
+ allowSeconds={this.props.allowSeconds}
91
+ fullWidth={this.props.fullWidth}
92
+ required={this.props.required}
93
+ />
94
+ </div>
91
95
  {this.props.preview !== true && (
92
96
  <IconButton
93
97
  disabled={this.props.disabled}
@@ -19,7 +19,7 @@ export class EditorButton extends React.PureComponent<IProps> {
19
19
  'icn-btn--small': this.props.size === 'small',
20
20
  });
21
21
  return (
22
- <Tooltip text={this.props.ariaValue} flow={this.props.toolTipFlow} appendToBody={this.props.toolTipAppend}>
22
+ <Tooltip text={this.props.ariaValue} flow={this.props.toolTipFlow}>
23
23
  <button
24
24
  id={this.props.id}
25
25
  tabIndex={0}
@@ -28,10 +28,8 @@ export class IconButton extends React.PureComponent<IProps> {
28
28
 
29
29
  return (
30
30
  <Tooltip
31
- text={this.props.ariaValue}
31
+ text={this.props.disabled ? null : this.props.ariaValue}
32
32
  flow={this.props.toolTipFlow}
33
- appendToBody={this.props.toolTipAppend}
34
- disabled={this.props.disabled}
35
33
  >
36
34
  <button
37
35
  id={this.props.id}
@@ -187,7 +187,7 @@ class TableList extends React.PureComponent<IProps, IState> {
187
187
  {
188
188
  (this.props.addItem && !this.props.readOnly)
189
189
  && <div className={`table-list__add-item table-list__item--margin`}>
190
- <Tooltip text='Add item' flow='top' appendToBody={true}>
190
+ <Tooltip text='Add item' flow='top'>
191
191
  <div className='table-list__add-item--container sd-margin-x--auto'>
192
192
  {this.dropDown()}
193
193
  </div>
@@ -233,7 +233,7 @@ class TableList extends React.PureComponent<IProps, IState> {
233
233
  {
234
234
  (this.props.addItem && !this.props.readOnly)
235
235
  && <div className={`table-list__add-item table-list__item--margin`}>
236
- <Tooltip text='Add item' flow='top' appendToBody={true}>
236
+ <Tooltip text='Add item' flow='top'>
237
237
  <div className='table-list__add-item--container sd-margin-x--auto'>
238
238
  {this.dropDown()}
239
239
  </div>
@@ -244,7 +244,7 @@ class TableList extends React.PureComponent<IProps, IState> {
244
244
  : (this.props.addItem && !this.props.readOnly)
245
245
  ? <div role='list' className={classes}>
246
246
  <div className={`table-list__add-item table-list__item--margin`}>
247
- <Tooltip text='Add item' flow='top' appendToBody={true}>
247
+ <Tooltip text='Add item' flow='top'>
248
248
  <div className='table-list__add-item--container sd-margin-x--auto'>
249
249
  {this.dropDown()}
250
250
  </div>
@@ -354,7 +354,7 @@ class TableListItem extends React.PureComponent<IPropsItem> {
354
354
  {
355
355
  this.props.addItem
356
356
  && <div className='table-list__add-bar-container'>
357
- <Tooltip text='Add item' flow='top' appendToBody={true}>
357
+ <Tooltip text='Add item' flow='top'>
358
358
  <div className='table-list__add-bar'>
359
359
  <Dropdown
360
360
  onChange={this.props.onAddItem}
@@ -61,7 +61,6 @@ export class Switch extends React.PureComponent<IProps> {
61
61
  <Tooltip
62
62
  text={this.props.label.content}
63
63
  flow={this.props.toolTipFlow}
64
- appendToBody={this.props.toolTipAppend}
65
64
  >
66
65
  <span className="sd-switch__wrapper" tabIndex={-1}>
67
66
  {checkboxInput}
@@ -1,59 +1,87 @@
1
1
  import * as React from 'react';
2
- import nextId from "react-id-generator";
3
- import { Tooltip as PRTooltip } from '@superdesk/primereact/tooltip';
2
+ import tippy, {Instance, Placement} from 'tippy.js';
3
+ import {assertNever} from '../helpers';
4
+
4
5
  interface IProps {
5
- text: string;
6
+ text: string | undefined | null;
6
7
  flow?: 'top' | 'left' | 'right' | 'down'; // defaults to 'top'
7
- appendToBody?: boolean;
8
- disabled?: boolean;
9
8
  }
10
9
 
11
- export const Tooltip: React.FC<IProps> = ({appendToBody, children, disabled, ...otherProps}) =>
12
- disabled
13
- ? <React.Fragment>{children}</React.Fragment>
14
- : appendToBody
15
- ? <TooltipAppended {...otherProps}>{children}</TooltipAppended>
16
- : <TooltipBasic {...otherProps}>{children}</TooltipBasic>;
17
-
18
- class TooltipBasic extends React.PureComponent<IProps> {
19
- htmlId = nextId();
20
- componentDidMount() {
21
- let tooltip = document.getElementById('t' + this.htmlId);
22
- tooltip?.setAttribute('data-sd-tooltip', this.props.text);
23
- if (this.props.flow) {
24
- tooltip?.setAttribute('data-flow', this.props.flow);
25
- }
26
- }
27
- render() {
28
- if (React.isValidElement(this.props.children)) {
29
- const attrs = {id: 't' + this.htmlId};
30
-
31
- return (
32
- React.cloneElement(this.props.children, attrs)
33
- );
34
- } else {
35
- return (
36
- <React.Fragment />
37
- );
38
- }
10
+ function flowToPlacement(flow: IProps['flow']): Placement | undefined {
11
+ switch (flow) {
12
+ case undefined:
13
+ return undefined;
14
+ case 'top':
15
+ return 'top';
16
+ case 'right':
17
+ return 'right';
18
+ case 'down':
19
+ return 'bottom';
20
+ case 'left':
21
+ return 'left';
22
+ default:
23
+ return assertNever(flow);
39
24
  }
40
25
  }
41
26
 
42
- const TooltipAppended: React.FC<IProps> = ({children, flow, text}) => {
43
- const triggerId = `uif-tooltip-${Math.random().toString().slice(2)}`;
44
- const position = flow === "down" ? "bottom" : flow;
27
+ export class Tooltip extends React.PureComponent<IProps> {
28
+ private id: string;
29
+ private instance: Instance | null;
45
30
 
46
- return (
47
- <React.Fragment>
48
- <PRTooltip target={"#" + triggerId} content={text} position={position ?? 'top'}/>
49
- { React.isValidElement(children)
50
- ? (() => {
51
- const attrs = {id: triggerId};
31
+ constructor(props: IProps) {
32
+ super(props);
52
33
 
53
- return React.cloneElement(children, attrs);
54
- })()
55
- : <React.Fragment />
34
+ this.id = 'tooltip-' + Math.random().toString().slice(2);
35
+ this.instance = null;
36
+ }
37
+
38
+ private setupTooltip() {
39
+ const placement = flowToPlacement(this.props.flow ?? 'top');
40
+ const content = this.props.text;
41
+
42
+ if (this.instance == null) {
43
+ this.instance = tippy('#' + this.id, {
44
+ placement: placement,
45
+ })[0];
46
+
47
+ if (content != null) {
48
+ this.instance.setContent(content);
49
+ } else {
50
+ this.instance.hide();
51
+ this.instance.disable();
56
52
  }
57
- </React.Fragment>
58
- );
59
- };
53
+ }
54
+
55
+ const willBeEnabled = content != null;
56
+ const isEnabled = this.instance.state.isEnabled;
57
+
58
+ if (isEnabled && willBeEnabled) {
59
+ this.instance.setContent(content);
60
+ } else if (isEnabled) {
61
+ // enabled now, but needs to be disabled
62
+ this.instance.hide();
63
+ this.instance.disable();
64
+ } else if (willBeEnabled) {
65
+ // disabled now, but needs to be enabled
66
+ this.instance.setContent(content);
67
+ this.instance.enable();
68
+ this.instance.show();
69
+ }
70
+ }
71
+
72
+ componentDidMount(): void {
73
+ this.setupTooltip();
74
+ }
75
+
76
+ componentDidUpdate(): void {
77
+ this.setupTooltip();
78
+ }
79
+
80
+ render() {
81
+ return (
82
+ <div id={this.id} style={{display: 'inline-flex'}}>
83
+ {this.props.children}
84
+ </div>
85
+ );
86
+ }
87
+ }
@@ -96,6 +96,13 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
96
96
  private popperInstance: Instance | null;
97
97
  private zIndex: number = getNextZIndex();
98
98
 
99
+ /*
100
+ * This variable is used to distinguish changes coming from outside (from props.value)
101
+ * from those made by the user through interaction with the component.
102
+ * It prevents triggering `onChange` when `props.value` updates externally.
103
+ */
104
+ private changesFromOutside: boolean;
105
+
99
106
  constructor(props: IProps<T>) {
100
107
  super(props);
101
108
  this.state = {
@@ -133,6 +140,7 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
133
140
  this.treeSelectRef = React.createRef();
134
141
  this.popperInstance = null;
135
142
  this.onDragEnd = this.onDragEnd.bind(this);
143
+ this.changesFromOutside = false;
136
144
  }
137
145
 
138
146
  inputFocus = () => {
@@ -212,8 +220,13 @@ export class TreeSelect<T> extends React.Component<IProps<T>, IState<T>> {
212
220
 
213
221
  componentDidUpdate(prevProps: Readonly<IProps<T>>, prevState: Readonly<IState<T>>): void {
214
222
  if (!isEqual(prevState.value, this.state.value)) {
215
- this.props.onChange(this.state.value);
223
+ if (this.changesFromOutside) {
224
+ this.changesFromOutside = false;
225
+ } else {
226
+ this.props.onChange(this.state.value);
227
+ }
216
228
  } else if (!isEqual(prevProps.value, this.props.value)) {
229
+ this.changesFromOutside = true;
217
230
  this.setState({
218
231
  value: this.props.value ?? [],
219
232
  });
@@ -31,7 +31,7 @@ export default class TooltipDoc extends React.Component {
31
31
  </Tooltip>
32
32
  <Tooltip text="Right on!" flow='right'>
33
33
  <Button text='right' onClick={() => false} />
34
- </Tooltip>
34
+ </Tooltip>
35
35
  </div>
36
36
  </Markup.ReactMarkupPreview>
37
37
  <Markup.ReactMarkupCode>{`
@@ -51,53 +51,10 @@ export default class TooltipDoc extends React.Component {
51
51
  </Markup.ReactMarkupCode>
52
52
  </Markup.ReactMarkup>
53
53
 
54
- <h3 className="docs-page__h3">Appended to body</h3>
55
- <p className="docs-page__paragraph">
56
- Appends tooltip element to body therefore avoids overflow issues. For performance reasons don't use it if not necessary.
57
- </p>
58
- <Markup.ReactMarkup>
59
- <Markup.ReactMarkupPreview>
60
-
61
- <div className="docs-page__content-row docs-page__content-row--no-margin">
62
-
63
- <div className="docs-page__content-row docs-page__content-row--no-margin">
64
- <Tooltip text="I'm appended to body" flow='top' appendToBody={true}>
65
- <Button text='top' onClick={() => false} />
66
- </Tooltip>
67
- <Tooltip text="I'm appended to body" flow='down' appendToBody={true}>
68
- <Button text='Down' onClick={() => false} />
69
- </Tooltip>
70
- <Tooltip text="I'm appended to body" flow='left' appendToBody={true}>
71
- <Button text='left' onClick={() => false} />
72
- </Tooltip>
73
- <Tooltip text="I'm appended to body" flow='right' appendToBody={true}>
74
- <Button text='right' onClick={() => false} />
75
- </Tooltip>
76
- </div>
77
- </div>
78
- </Markup.ReactMarkupPreview>
79
- <Markup.ReactMarkupCode>{`
80
- <Tooltip text="I'm appended to body" flow='top' appendToBody={true}>
81
- <Button text='top' onClick={() => false} />
82
- </Tooltip>
83
- <Tooltip text="I'm appended to body" flow='down' appendToBody={true}>
84
- <Button text='Down' onClick={() => false} />
85
- </Tooltip>
86
- <Tooltip text="I'm appended to body" flow='left' appendToBody={true}>
87
- <Button text='left' onClick={() => false} />
88
- </Tooltip>
89
- <Tooltip text="I'm appended to body" flow='right' appendToBody={true}>
90
- <Button text='right' onClick={() => false} />
91
- </Tooltip>
92
- `}
93
- </Markup.ReactMarkupCode>
94
- </Markup.ReactMarkup>
95
-
96
54
  <h3 className="docs-page__h3">Props</h3>
97
55
  <PropsList>
98
56
  <Prop name='text' isRequired={true} type='string' default='/' description='Tooltip text value.' />
99
57
  <Prop name='flow' isRequired={false} type='top | left | right | down' default='top' description='Position of tooltip text.' />
100
- <Prop name='appendToBody' isRequired={false} type='boolean' default='false' description='Appends element to body therefore avoids overflow issues.' />
101
58
  </PropsList>
102
59
  </section>
103
60
  )
@@ -12,7 +12,7 @@ interface IState {
12
12
  arr: any;
13
13
  }
14
14
 
15
- let options = [
15
+ const multiSelectOptions = [
16
16
  {
17
17
  value: {name: 'Category1'},
18
18
  children: [
@@ -93,7 +93,7 @@ let options = [
93
93
  },
94
94
  ]
95
95
 
96
- let options2 = [
96
+ const singleSelectOptions = [
97
97
  {
98
98
  value: {name: 'Category1', border: 'red'},
99
99
  children: [
@@ -169,9 +169,6 @@ let options2 = [
169
169
  },
170
170
  ]
171
171
 
172
- let fetchedArr = [];
173
- fetch('https://nominatim.openstreetmap.org/search/berlin?format=json&addressdetails=1&limit=20').then(response => response.json()).then(data => fetchedArr = data);
174
-
175
172
  type ICancelFn = () => void;
176
173
 
177
174
  function searchOptions(
@@ -181,9 +178,7 @@ function searchOptions(
181
178
  let timeout = setTimeout(() => {
182
179
 
183
180
  callback(
184
- fetchedArr
185
- .filter((item: any) => item.display_name.toLocaleLowerCase().includes(term.toLocaleLowerCase()))
186
- .map((item) => ({value: item})),
181
+ multiSelectOptions.filter((item: any) => item.value.name.toLocaleLowerCase().includes(term.toLocaleLowerCase()))
187
182
  );
188
183
  }, 1000);
189
184
 
@@ -198,8 +193,8 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
198
193
  this.state = {
199
194
  value: [],
200
195
  value2: [],
201
- options: options,
202
- options2: options,
196
+ options: multiSelectOptions,
197
+ options2: multiSelectOptions,
203
198
  inputValue: '',
204
199
  arr: []
205
200
  }
@@ -244,7 +239,7 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
244
239
  <TreeSelect
245
240
  kind='synchronous'
246
241
  value={[{name: 'Category1'}]}
247
- getOptions={() => options}
242
+ getOptions={() => multiSelectOptions}
248
243
  getId={(item) => item.name}
249
244
  getLabel={(item) => item.name}
250
245
  selectBranchWithChildren
@@ -300,7 +295,7 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
300
295
  <TreeSelect
301
296
  kind='synchronous'
302
297
  value={[{name: 'Category1'}, {name: 'Category2'}, {name: 'Category3'}]}
303
- getOptions={() => options}
298
+ getOptions={() => multiSelectOptions}
304
299
  getId={(item) => item.name}
305
300
  getLabel={(item) => item.name}
306
301
  selectBranchWithChildren
@@ -358,8 +353,8 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
358
353
  <TreeSelect
359
354
  kind="asynchronous"
360
355
  value={this.state.value}
361
- getLabel={({display_name}) => display_name}
362
- getId={({display_name}) => display_name}
356
+ getLabel={({name}) => name}
357
+ getId={({name}) => name}
363
358
  selectBranchWithChildren
364
359
  allowMultiple
365
360
  searchOptions={searchOptions}
@@ -400,7 +395,7 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
400
395
  <TreeSelect
401
396
  kind='synchronous'
402
397
  value={[]}
403
- getOptions={() => options2}
398
+ getOptions={() => singleSelectOptions}
404
399
  getLabel={(item) => item.name}
405
400
  getId={(item) => item.name}
406
401
  getBorderColor={(item) => item.border}