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', () => {