From e00cf0354e778c85ff8ce29b295b2fb74b480005 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 21 Oct 2022 18:39:40 +0200 Subject: [PATCH] fix(ui) - tooltip click issue --- .../DashboardView/DashboardView.tsx | 326 ++++++++++-------- .../app/components/Session_/Issues/Issues.js | 102 +++--- frontend/app/components/Session_/Subheader.js | 4 +- .../SessionCopyLink/SessionCopyLink.tsx | 30 +- .../shared/SharePopup/SharePopup.js | 62 ++-- .../ui/AnimatedTooltip/AnimatedTooltip.tsx | 69 ++++ .../components/ui/AnimatedTooltip/index.ts | 1 + frontend/app/components/ui/index.js | 1 + 8 files changed, 359 insertions(+), 236 deletions(-) create mode 100644 frontend/app/components/ui/AnimatedTooltip/AnimatedTooltip.tsx create mode 100644 frontend/app/components/ui/AnimatedTooltip/index.ts diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index b870435b7..a61b06a5e 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -21,172 +21,192 @@ import AddMetricContainer from '../DashboardWidgetGrid/AddMetricContainer'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; interface IProps { - siteId: string; - dashboardId: any; - renderReport?: any; + siteId: string; + dashboardId: any; + renderReport?: any; } type Props = IProps & RouteComponentProps; function DashboardView(props: Props) { - const { siteId, dashboardId } = props; - const { dashboardStore } = useStore(); - const { showModal } = useModal(); + const { siteId, dashboardId } = props; + const { dashboardStore } = useStore(); + const { showModal } = useModal(); - const [showTooltip, setShowTooltip] = React.useState(false); - const [focusTitle, setFocusedInput] = React.useState(true); - const [showEditModal, setShowEditModal] = React.useState(false); + const [showTooltip, setShowTooltip] = React.useState(false); + const [focusTitle, setFocusedInput] = React.useState(true); + const [showEditModal, setShowEditModal] = React.useState(false); - const showAlertModal = dashboardStore.showAlertModal; - const loading = dashboardStore.fetchingDashboard; - const dashboard: any = dashboardStore.selectedDashboard; - const period = dashboardStore.period; + const showAlertModal = dashboardStore.showAlertModal; + const loading = dashboardStore.fetchingDashboard; + const dashboard: any = dashboardStore.selectedDashboard; + const period = dashboardStore.period; - const queryParams = new URLSearchParams(props.location.search); + const queryParams = new URLSearchParams(props.location.search); - const trimQuery = () => { - if (!queryParams.has('modal')) return; - queryParams.delete('modal'); - props.history.replace({ - search: queryParams.toString(), - }); - }; - const pushQuery = () => { - if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); - }; + const trimQuery = () => { + if (!queryParams.has('modal')) return; + queryParams.delete('modal'); + props.history.replace({ + search: queryParams.toString(), + }); + }; + const pushQuery = () => { + if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); + }; - useEffect(() => { - if (queryParams.has('modal')) { - onAddWidgets(); - trimQuery(); - } - }, []); + useEffect(() => { + if (queryParams.has('modal')) { + onAddWidgets(); + trimQuery(); + } + }, []); - useEffect(() => { - const isExists = dashboardStore.getDashboardById(dashboardId); - if (!isExists) { - props.history.push(withSiteId(`/dashboard`, siteId)); - } - }, [dashboardId]); + useEffect(() => { + const isExists = dashboardStore.getDashboardById(dashboardId); + if (!isExists) { + props.history.push(withSiteId(`/dashboard`, siteId)); + } + }, [dashboardId]); - useEffect(() => { - if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId); - }, [dashboard]); + useEffect(() => { + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.fetch(dashboard.dashboardId); + }, [dashboard]); - const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard); - showModal(, { right: true }); - }; - - const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard); - setFocusedInput(isTitle); - setShowEditModal(true); - }; - - const onDelete = async () => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?`, - }) - ) { - dashboardStore.deleteDashboard(dashboard).then(() => { - props.history.push(withSiteId(`/dashboard`, siteId)); - }); - } - }; - - if (!dashboard) return null; - - return ( - -
- setShowEditModal(false)} focusTitle={focusTitle} /> - -
-
- - {dashboard?.name} - - } - onDoubleClick={() => onEdit(true)} - className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" - actionButton={ - /* @ts-ignore */ - - setShowTooltip(false)}> - setShowTooltip(false)} isPopup siteId={siteId} /> - -
- } - > - - - } - /> -
-
-
- dashboardStore.setPeriod(period)} - right={true} - /> -
-
-
- -
-
-
-
- {/* @ts-ignore */} - -

onEdit(false)} - > - {dashboard?.description || 'Describe the purpose of this dashboard'} -

-
-
- - dashboardStore.updateKey('showAlertModal', false)} /> -
-
+ const onAddWidgets = () => { + dashboardStore.initDashboard(dashboard); + showModal( + , + { right: true } ); + }; + + const onEdit = (isTitle: boolean) => { + dashboardStore.initDashboard(dashboard); + setFocusedInput(isTitle); + setShowEditModal(true); + }; + + const onDelete = async () => { + if ( + await confirm({ + header: 'Confirm', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { + dashboardStore.deleteDashboard(dashboard).then(() => { + props.history.push(withSiteId(`/dashboard`, siteId)); + }); + } + }; + + if (!dashboard) return null; + + return ( + +
+ setShowEditModal(false)} + focusTitle={focusTitle} + /> + +
+
+ + {dashboard?.name} + + } + onDoubleClick={() => onEdit(true)} + className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" + actionButton={ + setShowTooltip(false)}> + + setShowTooltip(false)} + isPopup + siteId={siteId} + /> +
+ } + > + + + + } + /> +
+
+
+ dashboardStore.setPeriod(period)} + right={true} + /> +
+
+
+ +
+
+
+
+ {/* @ts-ignore */} + +

onEdit(false)} + > + {dashboard?.description || 'Describe the purpose of this dashboard'} +

+
+
+ + dashboardStore.updateKey('showAlertModal', false)} + /> +
+
+ ); } // @ts-ignore -export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView))))); +export default withPageTitle('Dashboards - OpenReplay')( + withReport(withRouter(withModal(observer(DashboardView)))) +); diff --git a/frontend/app/components/Session_/Issues/Issues.js b/frontend/app/components/Session_/Issues/Issues.js index 200b5b278..30be2e5c6 100644 --- a/frontend/app/components/Session_/Issues/Issues.js +++ b/frontend/app/components/Session_/Issues/Issues.js @@ -5,24 +5,27 @@ import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; import IssuesModal from './IssuesModal'; import { fetchProjects, fetchMeta } from 'Duck/assignments'; import stl from './issues.module.css'; -import { Tooltip } from 'react-tippy' +import { Tooltip } from 'react-tippy'; -@connect(state => ({ - issues: state.getIn(['assignments', 'list']), - metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']), - projects: state.getIn(['assignments', 'projects']), - projectsFetched: state.getIn(['assignments', 'projectsFetched']), - activeIssue: state.getIn(['assignments', 'activeIssue']), - fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']), - fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']), - projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']), - issuesIntegration: state.getIn([ 'issues', 'list']).first() || {}, +@connect( + (state) => ({ + issues: state.getIn(['assignments', 'list']), + metaLoading: state.getIn(['assignments', 'fetchMeta', 'loading']), + projects: state.getIn(['assignments', 'projects']), + projectsFetched: state.getIn(['assignments', 'projectsFetched']), + activeIssue: state.getIn(['assignments', 'activeIssue']), + fetchIssueLoading: state.getIn(['assignments', 'fetchAssignment', 'loading']), + fetchIssuesLoading: state.getIn(['assignments', 'fetchAssignments', 'loading']), + projectsLoading: state.getIn(['assignments', 'fetchProjects', 'loading']), + issuesIntegration: state.getIn(['issues', 'list']).first() || {}, - jiraConfig: state.getIn([ 'issues', 'list' ]).first(), - issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]), -}), { fetchMeta, fetchProjects }) + jiraConfig: state.getIn(['issues', 'list']).first(), + issuesFetched: state.getIn(['issues', 'issuesFetched']), + }), + { fetchMeta, fetchProjects } +) class Issues extends React.Component { - state = {showModal: false }; + state = { showModal: false }; constructor(props) { super(props); @@ -31,64 +34,79 @@ class Issues extends React.Component { closeModal = () => { this.setState({ showModal: false }); - } + }; showIssuesList = (e) => { e.preventDefault(); e.stopPropagation(); this.setState({ showModal: true }); - } + }; handleOpen = () => { this.setState({ showModal: true }); - if (!this.props.projectsFetched) { // cache projects fetch - this.props.fetchProjects().then(function() { - const { projects } = this.props; - if (projects && projects.first()) { - this.props.fetchMeta(projects.first().id) - } - }.bind(this)) + if (!this.props.projectsFetched) { + // cache projects fetch + this.props.fetchProjects().then( + function () { + const { projects } = this.props; + if (projects && projects.first()) { + this.props.fetchMeta(projects.first().id); + } + }.bind(this) + ); } - } + }; render() { const { - sessionId, isModalDisplayed, projectsLoading, metaLoading, fetchIssuesLoading, issuesIntegration + sessionId, + isModalDisplayed, + projectsLoading, + metaLoading, + fetchIssuesLoading, + issuesIntegration, } = this.props; - const provider = issuesIntegration.provider + const provider = issuesIntegration.provider; return ( -
-
+
+
+ +
- +
} > -
- - Create Issue -
+
+ + Create Issue +
-
+
+
); } -}; +} export default Issues; diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 9e10aaf45..446311a29 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -78,8 +78,6 @@ function SubHeader(props) { ); } -const SubH = connectPlayer( - (state) => ({ currentLocation: state.location }) -)(SubHeader); +const SubH = connectPlayer((state) => ({ currentLocation: state.location }))(SubHeader); export default React.memo(SubH); diff --git a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx index bc8c09830..fccbb6b81 100644 --- a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx +++ b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { IconButton } from 'UI'; +import { Button, Icon } from 'UI'; import copy from 'copy-to-clipboard'; import { connectPlayer } from 'Player'; @@ -7,12 +7,13 @@ interface Props { content: string; time: any; } + function SessionCopyLink({ content = '', time }: Props) { - const [copied, setCopied] = React.useState(false) + const [copied, setCopied] = React.useState(false); const copyHandler = () => { setCopied(true); - copy(window.location.origin + window.location.pathname + '?jumpto=' + Math.round(time)); + copy(window.location.origin + window.location.pathname + '?jumpto=' + Math.round(time)); setTimeout(() => { setCopied(false); }, 1000); @@ -20,10 +21,25 @@ function SessionCopyLink({ content = '', time }: Props) { return (
- - { copied &&
Copied
} + {/* */} + + {copied &&
Copied
}
- ) + ); } -export default SessionCopyLink +const SessionCopyLinkCompo = connectPlayer((state: any) => ({ + time: state.time, +}))(SessionCopyLink); + +export default React.memo(SessionCopyLinkCompo); diff --git a/frontend/app/components/shared/SharePopup/SharePopup.js b/frontend/app/components/shared/SharePopup/SharePopup.js index 659deffb8..43e968479 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.js +++ b/frontend/app/components/shared/SharePopup/SharePopup.js @@ -13,9 +13,9 @@ import cn from 'classnames'; import { fetchList, init } from 'Duck/integrations/slack'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; -@connectPlayer((state) => ({ - time: state.time, -})) +// @connectPlayer((state) => ({ +// time: state.time, +// })) @connect( (state) => ({ channels: state.getIn(['slack', 'list']), @@ -58,7 +58,7 @@ export default class SharePopup extends React.PureComponent { }; handleSuccess = () => { - this.setState({ isOpen: false, comment: '' }) + this.setState({ isOpen: false, comment: '' }); toast.success('Sent to Slack.'); }; @@ -69,31 +69,31 @@ export default class SharePopup extends React.PureComponent { }; render() { - const { trigger, loading, channels, showCopyLink = false, time } = this.props; + const { trigger, loading, channels, showCopyLink = false } = this.props; const { comment, channelId, isOpen } = this.state; const options = channels .map(({ webhookId, name }) => ({ value: webhookId, label: name })) .toJS(); return ( - { - this.setState({ isOpen: false }) - }} - > + { + this.setState({ isOpen: false }); + }} + > +
Share this session link to Slack
@@ -105,7 +105,7 @@ export default class SharePopup extends React.PureComponent {
{showCopyLink && (
- +
)} @@ -132,7 +132,7 @@ export default class SharePopup extends React.PureComponent { className="mr-4" />
-
- +
)} - - } - > - {trigger} - + } + > +
{trigger}
+ + ); } } diff --git a/frontend/app/components/ui/AnimatedTooltip/AnimatedTooltip.tsx b/frontend/app/components/ui/AnimatedTooltip/AnimatedTooltip.tsx new file mode 100644 index 000000000..784ee6464 --- /dev/null +++ b/frontend/app/components/ui/AnimatedTooltip/AnimatedTooltip.tsx @@ -0,0 +1,69 @@ +import React, { cloneElement, useMemo, useState } from 'react'; +import { + Placement, + offset, + flip, + shift, + autoUpdate, + useFloating, + useInteractions, + useHover, + useFocus, + useRole, + useDismiss, + useClick, +} from '@floating-ui/react-dom-interactions'; +import { mergeRefs } from 'react-merge-refs'; + +interface Props { + label: string; + placement?: Placement; + children: JSX.Element; +} + +function AnimatedTooltip({ children, label, placement = 'top' }: Props) { + const [open, setOpen] = useState(false); + + const { x, y, reference, floating, strategy, context } = useFloating({ + placement, + open, + onOpenChange: setOpen, + middleware: [offset(5), flip(), shift({ padding: 8 })], + whileElementsMounted: autoUpdate, + }); + + const { getReferenceProps, getFloatingProps } = useInteractions([ + // useHover(context), + useFocus(context), + useRole(context, { role: 'tooltip' }), + useDismiss(context), + useClick(context), + ]); + + // Preserve the consumer's ref + const ref = useMemo(() => mergeRefs([reference, (children as any).ref]), [reference, children]); + const ppp = getReferenceProps({ ref, ...children.props }); + // console.log('ppp', ppp); + return ( +
+ {/* {cloneElement(children, getReferenceProps({ ref, ...children.props }))} */} + + {open && ( +
+ {label} +
+ )} +
+ ); +} + +export default AnimatedTooltip; diff --git a/frontend/app/components/ui/AnimatedTooltip/index.ts b/frontend/app/components/ui/AnimatedTooltip/index.ts new file mode 100644 index 000000000..5ed196fd5 --- /dev/null +++ b/frontend/app/components/ui/AnimatedTooltip/index.ts @@ -0,0 +1 @@ +export { default } from './AnimatedTooltip'; diff --git a/frontend/app/components/ui/index.js b/frontend/app/components/ui/index.js index db7ea7d37..11cd00f1d 100644 --- a/frontend/app/components/ui/index.js +++ b/frontend/app/components/ui/index.js @@ -57,3 +57,4 @@ export { default as Input } from './Input'; export { default as Form } from './Form'; export { default as Modal } from './Modal'; export { default as Message } from './Message'; +export { default as AnimatedTooltip } from './AnimatedTooltip'; \ No newline at end of file