diff --git a/RELEASE.md b/RELEASE.md
index 9b70726658..945d9ad4ea 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -5,11 +5,13 @@ Please follow the established format:
- Use present tense (e.g. 'Add new feature')
- Include the ID number for the related PR (or PRs) in parentheses
-->
+
# Upcoming Release
## Bug fixes and other changes
- Fix dataset factory patterns in Experiment Tracking. (#1588)
+- Improved feedback for copy to clipboard feature. (#1614)
# Release 6.6.1
diff --git a/src/components/flowchart/flowchart.js b/src/components/flowchart/flowchart.js
index 2de73da16b..7b7495b9b4 100644
--- a/src/components/flowchart/flowchart.js
+++ b/src/components/flowchart/flowchart.js
@@ -658,7 +658,11 @@ export class FlowChart extends Component {
})}
ref={this.layerNamesRef}
/>
-
+
);
}
diff --git a/src/components/shareable-url-modal/shareable-url-modal.js b/src/components/shareable-url-modal/shareable-url-modal.js
index 2f3bc79690..9dfae0e9b1 100644
--- a/src/components/shareable-url-modal/shareable-url-modal.js
+++ b/src/components/shareable-url-modal/shareable-url-modal.js
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { toggleShareableUrlModal } from '../../actions';
-import modifiers from '../../utils/modifiers';
import { s3BucketRegions } from '../../config';
import Button from '../ui/button';
@@ -13,6 +12,7 @@ import Input from '../ui/input';
import LoadingIcon from '../icons/loading';
import Modal from '../ui/modal';
import MenuOption from '../ui/menu-option';
+import Tooltip from '../ui/tooltip';
import './shareable-url-modal.scss';
@@ -117,7 +117,10 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
const onCopyClick = () => {
window.navigator.clipboard.writeText(responseUrl);
setShowCopied(true);
- setTimeout(() => setShowCopied(false), 1500);
+
+ setTimeout(() => {
+ setShowCopied(false);
+ }, 1500);
};
const handleModalClose = () => {
@@ -246,24 +249,15 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
Hosted link
{responseUrl}
{window.navigator.clipboard && (
- <>
-
- Copied to clipboard.
-
+
{
icon={CopyIcon}
onClick={onCopyClick}
/>
- >
+
+
)}
diff --git a/src/components/shareable-url-modal/shareable-url-modal.scss b/src/components/shareable-url-modal/shareable-url-modal.scss
index fa177cce09..4dd47fbfa6 100644
--- a/src/components/shareable-url-modal/shareable-url-modal.scss
+++ b/src/components/shareable-url-modal/shareable-url-modal.scss
@@ -150,8 +150,6 @@
.pipeline-icon--container {
display: block;
- margin: 0 12px 0 16px;
- position: absolute;
right: 6px;
}
}
@@ -161,6 +159,11 @@
font-size: 16px;
line-height: 20px;
}
+
+ &__result-action {
+ position: relative;
+ margin: 0 12px 0 16px;
+ }
}
.shareable-url-timestamp {
diff --git a/src/components/ui/command-copier/command-copier.js b/src/components/ui/command-copier/command-copier.js
index ccfbec46b1..5e3e773254 100644
--- a/src/components/ui/command-copier/command-copier.js
+++ b/src/components/ui/command-copier/command-copier.js
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
-import modifiers from '../../../utils/modifiers';
import MetaDataValue from '../../metadata/metadata-value';
import IconButton from '../../ui/icon-button';
+import Tooltip from '../tooltip';
import CopyIcon from '../../icons/copy';
import './command-copier.scss';
@@ -11,6 +11,7 @@ const CommandCopier = ({ command, isCommand }) => {
const onCopyClick = () => {
window.navigator.clipboard.writeText(command);
setShowCopied(true);
+
setTimeout(() => setShowCopied(false), 1500);
};
@@ -18,30 +19,26 @@ const CommandCopier = ({ command, isCommand }) => {
{window.navigator.clipboard && isCommand && (
- <>
-
- Copied to clipboard.
-
-
- >
+
)}
);
diff --git a/src/components/ui/command-copier/command-copier.scss b/src/components/ui/command-copier/command-copier.scss
index 369eefc3b1..69db65dbe3 100644
--- a/src/components/ui/command-copier/command-copier.scss
+++ b/src/components/ui/command-copier/command-copier.scss
@@ -28,6 +28,7 @@
}
.toolbox {
+ position: relative;
display: block;
width: 26px;
height: 34px;
diff --git a/src/components/ui/tooltip/tooltip.js b/src/components/ui/tooltip/tooltip.js
index 394a405048..9c3ed59243 100644
--- a/src/components/ui/tooltip/tooltip.js
+++ b/src/components/ui/tooltip/tooltip.js
@@ -14,19 +14,46 @@ export const insertZeroWidthSpace = (text) =>
/**
* Display flowchart node tooltip
+ * @param {string} arrowSize Tooltip arrow size regular | small
+ * @param {boolean} centerArrow Where to center tooltip arrow or not
* @param {Object} chartSize Chart dimensions in pixels
+ * @param {boolean} noDelay Where to show the tooltip immediately or after 1 sec delay
+ * @param {Object} style Tooltip custom css
* @param {Object} targetRect event.target.getBoundingClientRect()
- * @param {Boolean} visible Whether to show the tooltip
* @param {String} text Tooltip display label
+ * @param {Boolean} visible Whether to show the tooltip
*/
-const Tooltip = ({ chartSize, targetRect, visible, text }) => {
- const { left, top, width, height, outerWidth, sidebarWidth } = chartSize;
- const isRight = targetRect.left - sidebarWidth > width / 2;
- const isTop = targetRect.top < height / 2;
- const xOffset = isRight ? targetRect.left - outerWidth : targetRect.left;
- const yOffset = isTop ? targetRect.top + targetRect.height : targetRect.top;
- const x = xOffset - left + targetRect.width / 2;
- const y = yOffset - top;
+const Tooltip = ({
+ arrowSize,
+ centerArrow,
+ chartSize,
+ noDelay,
+ style,
+ targetRect,
+ text,
+ visible,
+}) => {
+ let isTop = false,
+ isRight = false;
+ const isFlowchartTooltip = chartSize && Object.keys(chartSize).length;
+ const styles = { ...style };
+
+ if (isFlowchartTooltip) {
+ let x = 0,
+ y = 0;
+ const { left, top, width, height, outerWidth, sidebarWidth } = chartSize;
+
+ isRight = targetRect.left - sidebarWidth > width / 2;
+ isTop = targetRect.top < height / 2;
+
+ const xOffset = isRight ? targetRect.left - outerWidth : targetRect.left;
+ const yOffset = isTop ? targetRect.top + targetRect.height : targetRect.top;
+
+ x = xOffset - left + targetRect.width / 2;
+ y = yOffset - top;
+
+ styles.transform = `translate(${x}px, ${y}px)`;
+ }
return (
{
'pipeline-tooltip--visible': visible,
'pipeline-tooltip--right': isRight,
'pipeline-tooltip--top': isTop,
+ 'pipeline-tooltip--chart': isFlowchartTooltip,
+ 'pipeline-tooltip--no-delay': noDelay,
+ 'pipeline-tooltip--center-arrow': centerArrow,
+ 'pipeline-tooltip--small-arrow': arrowSize === 'small',
})}
- style={{ transform: `translate(${x}px, ${y}px)` }}
+ style={styles}
>
{insertZeroWidthSpace(text)}
@@ -43,10 +74,14 @@ const Tooltip = ({ chartSize, targetRect, visible, text }) => {
};
Tooltip.defaultProps = {
+ arrowSize: 'regular',
+ centerArrow: false,
chartSize: {},
+ noDelay: false,
+ style: {},
targetRect: {},
- visible: false,
text: '',
+ visible: false,
};
export default Tooltip;
diff --git a/src/components/ui/tooltip/tooltip.scss b/src/components/ui/tooltip/tooltip.scss
index 8b9a67092a..146fe77dff 100644
--- a/src/components/ui/tooltip/tooltip.scss
+++ b/src/components/ui/tooltip/tooltip.scss
@@ -3,18 +3,27 @@
$x-offset: 20px;
$y-offset: 10px;
$triangle-size: 10px;
+$triangle-size-sm: 5px;
.pipeline-tooltip {
position: absolute;
- top: -$y-offset;
- left: -$x-offset;
+ bottom: calc(100% + 10px);
+ left: 50%;
z-index: 9;
- width: 100vw;
+ transform: translate(-50%, 0);
+ font-size: 12px;
visibility: hidden;
opacity: 0;
transition: opacity ease 0.1s, visibility ease 0.1s 1s;
pointer-events: none;
+ &--chart {
+ width: 100vw;
+ top: -$y-offset;
+ left: -$x-offset;
+ bottom: auto;
+ }
+
&--visible {
visibility: visible;
opacity: 1;
@@ -29,9 +38,23 @@ $triangle-size: 10px;
top: $y-offset;
}
+ &--no-delay {
+ transition-delay: 0s;
+ }
+}
+
+.pipeline-tooltip__text {
+ @extend %tooltip;
+
+ position: relative;
+ width: max-content;
+ max-width: calc(50vw - 150px);
+ padding: 8px 12px;
+ overflow-wrap: break-word;
+
&::after {
position: absolute;
- bottom: -$y-offset + 0.5;
+ bottom: calc(-#{$y-offset} + 0.5px);
left: calc($x-offset / 2);
width: 0;
height: 0;
@@ -42,38 +65,61 @@ $triangle-size: 10px;
content: '';
}
- &--right::after {
- right: calc($x-offset / 2);
+ .pipeline-tooltip--chart & {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ padding: 12px 20px;
+ }
+
+ .pipeline-tooltip--right.pipeline-tooltip--chart & {
+ right: 0;
left: auto;
+
+ &::after {
+ right: calc($x-offset / 2);
+ left: auto;
+ }
}
- &--top::after {
- top: -$y-offset + 0.5;
+ .pipeline-tooltip--top.pipeline-tooltip--chart & {
+ top: 0;
bottom: auto;
- border-width: 0 $triangle-size $triangle-size $triangle-size;
- border-top-color: transparent;
- border-bottom-color: var(--color-bg-alt);
+
+ &::after {
+ top: calc(-#{$y-offset} + 0.5px);
+ bottom: auto;
+ border-width: 0 $triangle-size $triangle-size $triangle-size;
+ border-top-color: transparent;
+ border-bottom-color: var(--color-bg-alt);
+ }
}
-}
-.pipeline-tooltip__text {
- @extend %tooltip;
+ .pipeline-tooltip--small-arrow & {
+ &::after {
+ bottom: calc(-#{$y-offset} / 2 + 0.5px);
+ border-width: $triangle-size-sm $triangle-size-sm 0 $triangle-size-sm;
+ }
+ }
- position: absolute;
- bottom: 0;
- left: 0;
- max-width: calc(50vw - 150px);
- padding: 12px 20px;
- font-size: 1.5em;
- overflow-wrap: break-word;
+ .pipeline-tooltip--top.pipeline-tooltip--small-arrow & {
+ &::after {
+ top: calc(-#{$y-offset} / 2 + 0.5px);
+ border-width: 0 $triangle-size-sm $triangle-size-sm $triangle-size-sm;
+ }
+ }
- .pipeline-tooltip--right & {
- right: 0;
- left: auto;
+ .pipeline-tooltip--center-arrow:not(.pipeline-tooltip--right) & {
+ &::after {
+ left: 50%;
+ transform: translateX(-50%);
+ }
}
- .pipeline-tooltip--top & {
- top: 0;
- bottom: auto;
+ .pipeline-tooltip--right.pipeline-tooltip--center-arrow & {
+ &::after {
+ top: 50%;
+ transform: translateY(-50%);
+ }
}
}
diff --git a/src/components/ui/tooltip/tooltip.test.js b/src/components/ui/tooltip/tooltip.test.js
index f6f5860687..262b1a3291 100644
--- a/src/components/ui/tooltip/tooltip.test.js
+++ b/src/components/ui/tooltip/tooltip.test.js
@@ -72,6 +72,48 @@ describe('Tooltip', () => {
const container = wrapper.find('.pipeline-tooltip--right');
expect(container.length).toBe(1);
});
+
+ it("should add the 'no-delay' class to the tooltip when noDelay prop is true", () => {
+ const targetRect = {
+ ...mockProps.targetRect,
+ left: mockProps.chartSize.width - 10 + globalToolbarWidth,
+ };
+ const wrapper = setup.shallow(Tooltip, {
+ ...mockProps,
+ targetRect,
+ noDelay: true,
+ });
+ const container = wrapper.find('.pipeline-tooltip--no-delay');
+ expect(container.length).toBe(1);
+ });
+
+ it("should add the 'center-arrow' class to the tooltip when centerArrow prop is true", () => {
+ const targetRect = {
+ ...mockProps.targetRect,
+ left: mockProps.chartSize.width - 10 + globalToolbarWidth,
+ };
+ const wrapper = setup.shallow(Tooltip, {
+ ...mockProps,
+ targetRect,
+ centerArrow: true,
+ });
+ const container = wrapper.find('.pipeline-tooltip--center-arrow');
+ expect(container.length).toBe(1);
+ });
+
+ it("should add the 'small-arrow' class to the tooltip when arrowSize prop is 'small'", () => {
+ const targetRect = {
+ ...mockProps.targetRect,
+ left: mockProps.chartSize.width - 10 + globalToolbarWidth,
+ };
+ const wrapper = setup.shallow(Tooltip, {
+ ...mockProps,
+ targetRect,
+ arrowSize: 'small',
+ });
+ const container = wrapper.find('.pipeline-tooltip--small-arrow');
+ expect(container.length).toBe(1);
+ });
});
describe('insertZeroWidthSpace', () => {