superdesk-ui-framework 4.0.86 → 4.0.88

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.
@@ -838,4 +838,31 @@ $icn-button-focus-box-shadow: 0 0 0 2px $sd-colour--focus-shadow;
838
838
  font-weight: 500;
839
839
  line-height: 1.2;
840
840
  text-align: center;
841
+ }
842
+
843
+ .headless-button {
844
+ display: flex;
845
+ align-items: center;
846
+ justify-content: center;
847
+ margin: 0;
848
+ padding: 0;
849
+ overflow: hidden;
850
+ outline: 1px solid transparent;
851
+ outline-offset: 0;
852
+ transition: all 0.2s ease;
853
+
854
+ &:hover,
855
+ &:focus-visible {
856
+ cursor: pointer;
857
+ outline-color: var(--sd-colour-interactive);
858
+ outline-offset: 1px;
859
+ }
860
+ &:active {
861
+ outline-width: 2px;
862
+ }
863
+ &:disabled {
864
+ opacity: 0.75;
865
+ cursor: not-allowed;
866
+ outline: none !important;
867
+ }
841
868
  }
@@ -26,7 +26,7 @@ $tag-label-palette: $tag-bg-colors !default;
26
26
 
27
27
  --tag-label-minwidth: 1.8em;
28
28
  --tag-label-radius: var(--b-radius--full);
29
- --tag-label-lineheight: 1.1;
29
+ --tag-label-lineheight: 1;
30
30
  }
31
31
 
32
32
  /// Generates the base styles for a badge.
@@ -109,7 +109,6 @@
109
109
  // Prime React
110
110
  @import '../../node_modules/@superdesk/primereact/resources/primereact.min.css';
111
111
  @import '../../node_modules/primeicons/primeicons.css';
112
- @import '~tippy.js/dist/tippy.css';
113
112
  @import 'pr-superdesk-theme';
114
113
  @import 'primereact/pr-dialog';
115
114
  @import 'primereact/pr-menu';
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import classNames from 'classnames';
3
3
  import {Icon} from './Icon';
4
4
  import {Spinner} from './Spinner';
5
- import {WithTooltip} from './Tooltip';
5
+ import {Tooltip} from './Tooltip';
6
6
 
7
7
  interface IPropsButton {
8
8
  text: string;
@@ -82,15 +82,15 @@ export class Button extends React.PureComponent<IPropsButton> {
82
82
 
83
83
  interface ITooltipWrapperProps {
84
84
  tooltipText: string | null | undefined;
85
- children: React.ComponentProps<typeof WithTooltip>['children'];
85
+ children: (options: {attributes: React.HTMLAttributes<HTMLElement>}) => React.ReactNode;
86
86
  }
87
87
 
88
88
  class TooltipWrapper extends React.PureComponent<ITooltipWrapperProps> {
89
89
  render() {
90
90
  const {tooltipText, children} = this.props;
91
91
 
92
- return (tooltipText ?? '').length > 0 ? (
93
- <WithTooltip text={tooltipText}>{({attributes}) => children({attributes})}</WithTooltip>
92
+ return tooltipText != null && (tooltipText ?? '').length > 0 ? (
93
+ <Tooltip content={tooltipText}>{({attributes}) => children({attributes})}</Tooltip>
94
94
  ) : (
95
95
  <>{children({attributes: {}})}</>
96
96
  );
@@ -0,0 +1,66 @@
1
+ import * as React from 'react';
2
+ import classNames from 'classnames';
3
+ import {Tooltip} from './Tooltip';
4
+
5
+ interface IPropsHeadlessButton {
6
+ onClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void;
7
+ tooltip?: string;
8
+ id?: string;
9
+ children?: React.ReactNode;
10
+ disabled?: boolean;
11
+ ariaLabel?: string;
12
+ radius?: 'x-small' | 'small' | 'medium' | 'full';
13
+ className?: string;
14
+ 'data-test-id'?: string;
15
+ }
16
+
17
+ export class HeadlessButton extends React.PureComponent<IPropsHeadlessButton> {
18
+ render() {
19
+ let classes = classNames(
20
+ 'headless-button',
21
+ {
22
+ 'radius-xs': this.props.radius === 'x-small',
23
+ 'radius-sm': this.props.radius === 'small',
24
+ 'radius-md': this.props.radius === 'medium',
25
+ 'radius-full': this.props.radius === 'full',
26
+ },
27
+ this.props.className,
28
+ );
29
+
30
+ return (
31
+ <TooltipWrapper tooltipText={this.props.tooltip}>
32
+ {({attributes}) => (
33
+ <button
34
+ id={this.props.id}
35
+ className={classes}
36
+ tabIndex={0}
37
+ disabled={this.props.disabled}
38
+ onClick={this.props.onClick}
39
+ aria-label={this.props.ariaLabel}
40
+ data-test-id={this.props['data-test-id']}
41
+ {...attributes}
42
+ >
43
+ {this.props.children}
44
+ </button>
45
+ )}
46
+ </TooltipWrapper>
47
+ );
48
+ }
49
+ }
50
+
51
+ interface ITooltipWrapperProps {
52
+ tooltipText: string | null | undefined;
53
+ children: (options: {attributes: React.HTMLAttributes<HTMLElement>}) => React.ReactNode;
54
+ }
55
+
56
+ class TooltipWrapper extends React.PureComponent<ITooltipWrapperProps> {
57
+ render() {
58
+ const {tooltipText, children} = this.props;
59
+
60
+ return tooltipText != null && (tooltipText ?? '').length > 0 ? (
61
+ <Tooltip content={tooltipText}>{({attributes}) => children({attributes})}</Tooltip>
62
+ ) : (
63
+ <>{children({attributes: {}})}</>
64
+ );
65
+ }
66
+ }
@@ -7,7 +7,7 @@ import {getNextZIndex} from '../zIndex';
7
7
 
8
8
  interface IPropsPopupPositioner {
9
9
  getReferenceElement(): HTMLElement;
10
- placement: Placement;
10
+ placement?: Placement;
11
11
  onClose(): void;
12
12
  shouldClose?: (event: MouseEvent | Event) => boolean;
13
13
  closeOnHoverEnd?: boolean;
@@ -137,8 +137,7 @@ export class PopupPositioner extends React.PureComponent<IPropsPopupPositioner>
137
137
  */
138
138
  setTimeout(() => {
139
139
  if (this.wrapperEl != null) {
140
- this.popper = createPopper(this.props.getReferenceElement(), this.wrapperEl, {
141
- placement: this.props.placement,
140
+ const options: Parameters<typeof createPopper>[2] = {
142
141
  modifiers: [
143
142
  restrictHeightToMaxAvailable,
144
143
  {
@@ -152,7 +151,13 @@ export class PopupPositioner extends React.PureComponent<IPropsPopupPositioner>
152
151
  maxSize,
153
152
  applyMaxSize,
154
153
  ],
155
- });
154
+ };
155
+
156
+ if (this.props.placement != null) {
157
+ options.placement = this.props.placement;
158
+ }
159
+
160
+ this.popper = createPopper(this.props.getReferenceElement(), this.wrapperEl, options);
156
161
  }
157
162
  }, 50);
158
163
  }
@@ -200,7 +205,7 @@ export class PopupPositioner extends React.PureComponent<IPropsPopupPositioner>
200
205
  */
201
206
  export function showPopup(
202
207
  referenceElement: HTMLElement,
203
- placement: Placement,
208
+ placement: Placement | undefined,
204
209
  Component: React.ComponentType<{closePopup(): void}>,
205
210
  closeOnHoverEnd?: boolean,
206
211
  onClose?: () => void,
@@ -1,15 +1,9 @@
1
1
  import * as React from 'react';
2
- import nextId from 'react-id-generator';
3
- import tippy, {Instance, Placement} from 'tippy.js';
4
2
  import {assertNever} from '../helpers';
3
+ import {IPropsTooltipV2, TooltipV2} from './TooltipV2';
4
+ import {Placement} from 'popper.js';
5
5
 
6
- interface IProps {
7
- text: string | undefined | null;
8
- flow?: 'top' | 'left' | 'right' | 'down'; // defaults to 'top'
9
- children(options: {attributes: {[name: string]: string}}): React.ReactNode;
10
- }
11
-
12
- function flowToPlacement(flow: IProps['flow']): Placement | undefined {
6
+ function flowToPlacement(flow: IPropsLegacy['flow']): Placement | undefined {
13
7
  switch (flow) {
14
8
  case undefined:
15
9
  return undefined;
@@ -26,83 +20,29 @@ function flowToPlacement(flow: IProps['flow']): Placement | undefined {
26
20
  }
27
21
  }
28
22
 
29
- const tooltipAttributeName = 'data-with-tooltip';
30
- const getTooltipSelector = (value: string) => `[${tooltipAttributeName}=${value}]`;
31
-
32
- export class WithTooltip extends React.PureComponent<IProps> {
33
- private id: string;
34
- private instance: Instance | null;
35
-
36
- constructor(props: IProps) {
37
- super(props);
38
-
39
- this.id = nextId();
40
-
41
- this.instance = null;
42
- }
43
-
44
- private setupTooltip() {
45
- const placement = flowToPlacement(this.props.flow ?? 'top');
46
- const content = this.props.text;
47
-
48
- if (this.instance == null) {
49
- this.instance = tippy(getTooltipSelector(this.id), {
50
- placement: placement,
51
- })[0];
52
-
53
- if (this.instance == null) {
54
- // prevent crashing in unit tests
55
- return;
56
- }
57
-
58
- if (content != null) {
59
- this.instance.setContent(content);
60
- } else {
61
- this.instance.hide();
62
- this.instance.disable();
63
- }
64
- }
65
-
66
- const willBeEnabled = content != null;
67
- const isEnabled = this.instance.state.isEnabled;
68
-
69
- if (isEnabled && willBeEnabled) {
70
- this.instance.setContent(content);
71
- } else if (isEnabled) {
72
- // enabled now, but needs to be disabled
73
- this.instance.hide();
74
- this.instance.disable();
75
- } else if (willBeEnabled) {
76
- // disabled now, but needs to be enabled
77
- this.instance.setContent(content);
78
- this.instance.enable();
79
- this.instance.show();
80
- }
81
- }
82
-
83
- componentDidMount(): void {
84
- this.setupTooltip();
85
- }
86
-
87
- componentDidUpdate(): void {
88
- this.setupTooltip();
89
- }
90
-
91
- render() {
92
- return this.props.children({attributes: {[tooltipAttributeName]: this.id}});
93
- }
23
+ interface IPropsLegacy {
24
+ text: string | undefined | null;
25
+ flow?: 'top' | 'left' | 'right' | 'down'; // defaults to 'top'
26
+ content?: never; // added for discriminated union support with IPropsTooltipV2
94
27
  }
95
28
 
96
- export class Tooltip extends React.PureComponent<Omit<IProps, 'children'>> {
29
+ type IProps = IPropsTooltipV2 | IPropsLegacy;
30
+
31
+ export class Tooltip extends React.PureComponent<IProps> {
97
32
  render() {
98
- return (
99
- <WithTooltip text={this.props.text} flow={this.props.flow}>
100
- {({attributes}) => (
101
- <div {...attributes} style={{display: 'inline-flex'}}>
102
- {this.props.children}
103
- </div>
104
- )}
105
- </WithTooltip>
106
- );
33
+ if (this.props.content != null) {
34
+ return <TooltipV2 {...this.props} />;
35
+ } else {
36
+ // backwards compatibility for legacy props
37
+ return (
38
+ <TooltipV2 content={this.props.text ?? ''} placement={flowToPlacement(this.props.flow)}>
39
+ {({attributes}) => (
40
+ <div {...attributes} style={{display: 'inline-flex'}}>
41
+ {this.props.children}
42
+ </div>
43
+ )}
44
+ </TooltipV2>
45
+ );
46
+ }
107
47
  }
108
48
  }
@@ -0,0 +1,75 @@
1
+ import * as React from 'react';
2
+ import {Placement} from '@popperjs/core';
3
+ import {WithPopover} from './WithPopover';
4
+
5
+ export interface IPropsTooltipV2 {
6
+ content: string | React.ComponentType;
7
+ placement?: Placement;
8
+
9
+ /**
10
+ * If unsure - use ReactNode.
11
+ * Function is for advanced use cases where it's needed to avoid the wrapping span.
12
+ */
13
+ children: React.ReactNode | ((options: {attributes: React.HTMLAttributes<HTMLElement>}) => React.ReactNode);
14
+ }
15
+
16
+ /**
17
+ * Component is intentionally not exported.
18
+ * It will be moved into the components/Tooltip.tsx when legacy support is dropped there.
19
+ */
20
+ export class TooltipV2 extends React.PureComponent<IPropsTooltipV2> {
21
+ render() {
22
+ return (
23
+ <WithPopover
24
+ placement={this.props.placement ?? 'top'}
25
+ component={() => {
26
+ return (
27
+ <div data-theme="dark-ui">
28
+ <div
29
+ style={{
30
+ background: 'var(--color-bg-100)',
31
+ color: 'var(--color-text)',
32
+ borderRadius: 'var(--b-radius--medium)',
33
+ paddingInline: 'var(--space--0-5)',
34
+ fontSize: 'var(--text-size-x-small)',
35
+ margin: 2,
36
+ }}
37
+ >
38
+ {(() => {
39
+ if (typeof this.props.content === 'string') {
40
+ return <span>{this.props.content}</span>;
41
+ } else {
42
+ const Component = this.props.content;
43
+
44
+ return <Component />;
45
+ }
46
+ })()}
47
+ </div>
48
+ </div>
49
+ );
50
+ }}
51
+ >
52
+ {(toggle) => {
53
+ const attributes: React.HTMLAttributes<HTMLElement> = {
54
+ onMouseOver: (event) => {
55
+ toggle(event.target as HTMLElement);
56
+ },
57
+ onMouseOut: (event) => {
58
+ toggle(event.target as HTMLElement);
59
+ },
60
+ };
61
+
62
+ if (typeof this.props.children === 'function') {
63
+ return this.props.children({attributes});
64
+ } else {
65
+ return (
66
+ <span {...attributes} style={{display: 'inline-flex'}}>
67
+ {this.props.children}
68
+ </span>
69
+ );
70
+ }
71
+ }}
72
+ </WithPopover>
73
+ );
74
+ }
75
+ }
@@ -4,7 +4,7 @@ import {showPopup} from './ShowPopup';
4
4
 
5
5
  export interface IPropsWithPopover {
6
6
  children(toggle: (referenceElement: HTMLElement) => void): React.ReactNode;
7
- placement: Placement;
7
+ placement?: Placement;
8
8
  component: React.ComponentType<{closePopup(): void}>;
9
9
  closeOnHoverEnd?: boolean;
10
10
  onClose?: () => void;
@@ -3,6 +3,7 @@
3
3
 
4
4
  export {HelloWorld} from './components/HelloWorld';
5
5
  export {Button} from './components/Button';
6
+ export {HeadlessButton} from './components/HeadlessButton';
6
7
  export {Input} from './components/Input';
7
8
  export {Select, Option} from './components/Select';
8
9
  export {SelectWithTemplate} from './components/SelectWithTemplate';
@@ -11,30 +11,25 @@ export default class TooltipDoc extends React.Component {
11
11
  <h2 className="docs-page__h2">Tooltips</h2>
12
12
  <Markup.ReactMarkupCodePreview>
13
13
  {`
14
- <Tooltip text="I'm on top" >
14
+ <Tooltip content="I'm on top" >
15
15
  <Button text='top' onClick={() => false} />
16
16
  </Tooltip>
17
17
  `}
18
18
  </Markup.ReactMarkupCodePreview>
19
19
  <h3 className="docs-page__h3">Default</h3>
20
- <p className="docs-page__paragraph">
21
- Chose one of 4 placement options (<code>’left’</code>, <code>’right’</code>, <code>down</code>, and{' '}
22
- <code>’top’</code>). The default value is <code>’top’</code> and will be rendered so without
23
- explicitly specifying it.
24
- </p>
25
20
  <Markup.ReactMarkup>
26
21
  <Markup.ReactMarkupPreview>
27
22
  <div className="docs-page__content-row docs-page__content-row--no-margin">
28
- <Tooltip text="I'm on top">
23
+ <Tooltip content="I'm on top">
29
24
  <Button text="top" onClick={() => false} />
30
25
  </Tooltip>
31
- <Tooltip text="I'm at the bottom" flow="down">
26
+ <Tooltip content="I'm at the bottom" placement="bottom">
32
27
  <Button text="bottom" onClick={() => false} />
33
28
  </Tooltip>
34
- <Tooltip text="I open on the left" flow="left">
29
+ <Tooltip content="I open on the left" placement="left">
35
30
  <Button text="left" onClick={() => false} />
36
31
  </Tooltip>
37
- <Tooltip text="Right on!" flow="right">
32
+ <Tooltip content="Right on!" placement="right">
38
33
  <Button text="right" onClick={() => false} />
39
34
  </Tooltip>
40
35
  </div>
@@ -57,17 +52,29 @@ export default class TooltipDoc extends React.Component {
57
52
  </Markup.ReactMarkupCode>
58
53
  </Markup.ReactMarkup>
59
54
 
60
- <h3 className="docs-page__h3">Props</h3>
61
- <PropsList>
62
- <Prop name="text" isRequired={true} type="string" default="/" description="Tooltip text value." />
63
- <Prop
64
- name="flow"
65
- isRequired={false}
66
- type="top | left | right | down"
67
- default="top"
68
- description="Position of tooltip text."
69
- />
70
- </PropsList>
55
+ <h3 className="docs-page__h3">With JSX as content</h3>
56
+ <Markup.ReactMarkup>
57
+ <Markup.ReactMarkupPreview>
58
+ <div className="docs-page__content-row docs-page__content-row--no-margin">
59
+ <Tooltip
60
+ content={() => (
61
+ <span>
62
+ hello <span style={{color: 'yellow'}}>world</span>
63
+ </span>
64
+ )}
65
+ >
66
+ <Button text="demo" onClick={() => false} />
67
+ </Tooltip>
68
+ </div>
69
+ </Markup.ReactMarkupPreview>
70
+ <Markup.ReactMarkupCode>
71
+ {`
72
+ <Tooltip content={() => <span>hello <span style={{color: 'yellow'}}>world</span></span>}>
73
+ <Button text="demo" onClick={() => false} />
74
+ </Tooltip>
75
+ `}
76
+ </Markup.ReactMarkupCode>
77
+ </Markup.ReactMarkup>
71
78
  </section>
72
79
  );
73
80
  }
@@ -375,7 +375,7 @@ export class TreeSelectDocs extends React.Component<{}, IState> {
375
375
  }}
376
376
  valueTemplate={(item: any, Wrapper) => {
377
377
  return (
378
- <Wrapper borderColor={item.border}>
378
+ <Wrapper backgroundColor={item.bgColor}>
379
379
  <span>{item.name}</span>
380
380
  </Wrapper>
381
381
  );