fix(ui) - tooltip click issue

This commit is contained in:
Shekar Siri 2022-10-21 18:39:40 +02:00
parent 317d18f3df
commit e00cf0354e
8 changed files with 359 additions and 236 deletions

View file

@ -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(<DashboardModal siteId={siteId} onMetricAdd={pushQuery} dashboardId={dashboardId} />, { 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 (
<Loader loading={loading}>
<div style={{ maxWidth: '1300px', margin: 'auto' }}>
<DashboardEditModal show={showEditModal} closeHandler={() => setShowEditModal(false)} focusTitle={focusTitle} />
<Breadcrumb
items={[
{
label: 'Dashboards',
to: withSiteId('/dashboard', siteId),
},
{ label: (dashboard && dashboard.name) || '' },
]}
/>
<div className="flex items-center mb-2 justify-between">
<div className="flex items-center" style={{ flex: 3 }}>
<PageTitle
title={
// @ts-ignore
<Tooltip delay={100} arrow title="Double click to rename">
{dashboard?.name}
</Tooltip>
}
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 */
<Tooltip
open={showTooltip}
interactive
useContext
// @ts-ignore
theme="nopadding"
hideDelay={0}
duration={0}
distance={20}
html={
<div style={{ padding: 0 }}>
<OutsideClickDetectingDiv onClickOutside={() => setShowTooltip(false)}>
<AddMetricContainer onAction={() => setShowTooltip(false)} isPopup siteId={siteId} />
</OutsideClickDetectingDiv>
</div>
}
>
<Button variant="primary" onClick={() => setShowTooltip(true)}>
Add Metric
</Button>
</Tooltip>
}
/>
</div>
<div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}>
<div className="flex items-center flex-shrink-0 justify-end" style={{ width: '300px' }}>
<SelectDateRange
style={{ width: '300px' }}
period={period}
onChange={(period: any) => dashboardStore.setPeriod(period)}
right={true}
/>
</div>
<div className="mx-4" />
<div className="flex items-center flex-shrink-0">
<DashboardOptions
editHandler={onEdit}
deleteHandler={onDelete}
renderReport={props.renderReport}
isTitlePresent={!!dashboard?.description}
/>
</div>
</div>
</div>
<div className="pb-4">
{/* @ts-ignore */}
<Tooltip delay={100} arrow title="Double click to rename" className='w-fit !block'>
<h2
className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
onDoubleClick={() => onEdit(false)}
>
{dashboard?.description || 'Describe the purpose of this dashboard'}
</h2>
</Tooltip>
</div>
<DashboardWidgetGrid siteId={siteId} dashboardId={dashboardId} onEditHandler={onAddWidgets} id="report" />
<AlertFormModal showModal={showAlertModal} onClose={() => dashboardStore.updateKey('showAlertModal', false)} />
</div>
</Loader>
const onAddWidgets = () => {
dashboardStore.initDashboard(dashboard);
showModal(
<DashboardModal siteId={siteId} onMetricAdd={pushQuery} dashboardId={dashboardId} />,
{ 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 (
<Loader loading={loading}>
<div style={{ maxWidth: '1300px', margin: 'auto' }}>
<DashboardEditModal
show={showEditModal}
closeHandler={() => setShowEditModal(false)}
focusTitle={focusTitle}
/>
<Breadcrumb
items={[
{
label: 'Dashboards',
to: withSiteId('/dashboard', siteId),
},
{ label: (dashboard && dashboard.name) || '' },
]}
/>
<div className="flex items-center mb-2 justify-between">
<div className="flex items-center" style={{ flex: 3 }}>
<PageTitle
title={
// @ts-ignore
<Tooltip delay={100} arrow title="Double click to rename">
{dashboard?.name}
</Tooltip>
}
onDoubleClick={() => onEdit(true)}
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
actionButton={
<OutsideClickDetectingDiv onClickOutside={() => setShowTooltip(false)}>
<Tooltip
open={showTooltip}
interactive
useContext
// @ts-ignore
theme="nopadding"
hideDelay={0}
duration={0}
distance={20}
html={
<div style={{ padding: 0 }}>
<AddMetricContainer
onAction={() => setShowTooltip(false)}
isPopup
siteId={siteId}
/>
</div>
}
>
<Button variant="primary" onClick={() => setShowTooltip(true)}>
Add Metric
</Button>
</Tooltip>
</OutsideClickDetectingDiv>
}
/>
</div>
<div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}>
<div className="flex items-center flex-shrink-0 justify-end" style={{ width: '300px' }}>
<SelectDateRange
style={{ width: '300px' }}
period={period}
onChange={(period: any) => dashboardStore.setPeriod(period)}
right={true}
/>
</div>
<div className="mx-4" />
<div className="flex items-center flex-shrink-0">
<DashboardOptions
editHandler={onEdit}
deleteHandler={onDelete}
renderReport={props.renderReport}
isTitlePresent={!!dashboard?.description}
/>
</div>
</div>
</div>
<div className="pb-4">
{/* @ts-ignore */}
<Tooltip delay={100} arrow title="Double click to rename" className="w-fit !block">
<h2
className="my-2 font-normal w-fit text-disabled-text border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
onDoubleClick={() => onEdit(false)}
>
{dashboard?.description || 'Describe the purpose of this dashboard'}
</h2>
</Tooltip>
</div>
<DashboardWidgetGrid
siteId={siteId}
dashboardId={dashboardId}
onEditHandler={onAddWidgets}
id="report"
/>
<AlertFormModal
showModal={showAlertModal}
onClose={() => dashboardStore.updateKey('showAlertModal', false)}
/>
</div>
</Loader>
);
}
// @ts-ignore
export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView)))));
export default withPageTitle('Dashboards - OpenReplay')(
withReport(withRouter(withModal(observer(DashboardView))))
);

View file

@ -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 (
<div className="relative">
<div className={ stl.buttonWrapper} onClick={this.handleOpen}>
<div className="relative">
<div className={stl.buttonWrapper}>
<OutsideClickDetectingDiv onClickOutside={this.closeModal}>
<Tooltip
open={this.state.showModal}
position="bottom"
interactive
// animation="shift"
trigger="click"
unmountHTMLWhenHide
// @ts-ignore
theme='light'
useContext
theme="light"
arrow
html={
<OutsideClickDetectingDiv onClickOutside={this.closeModal}>
<div>
<IssuesModal
provider={provider}
sessionId={ sessionId }
closeHandler={ this.closeModal }
sessionId={sessionId}
closeHandler={this.closeModal}
/>
</OutsideClickDetectingDiv>
</div>
}
>
<div className="flex items-center" onClick={this.handleOpen} disabled={!isModalDisplayed && (metaLoading || fetchIssuesLoading || projectsLoading)}>
<Icon name={ `integrations/${ provider === 'jira' ? 'jira' : 'github'}` } size="16" />
<span className="ml-2">Create Issue</span>
</div>
<div
className="flex items-center"
onClick={this.handleOpen}
disabled={
!isModalDisplayed && (metaLoading || fetchIssuesLoading || projectsLoading)
}
>
<Icon name={`integrations/${provider === 'jira' ? 'jira' : 'github'}`} size="16" />
<span className="ml-2">Create Issue</span>
</div>
</Tooltip>
</div>
</OutsideClickDetectingDiv>
</div>
</div>
);
}
};
}
export default Issues;

View file

@ -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);

View file

@ -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 (
<div className="flex justify-between items-center w-full mt-2">
<IconButton label="Copy URL at current time" primaryText icon="link-45deg" onClick={copyHandler} />
{ copied && <div className="color-gray-medium">Copied</div> }
{/* <IconButton
label="Copy URL at current time"
primaryText
icon="link-45deg"
onClick={copyHandler}
/> */}
<Button variant="text-primary" onClick={copyHandler}>
<>
<Icon name="link-45deg" className="mr-2" color="teal" size="18" />
<span>Copy URL at current time</span>
</>
</Button>
{copied && <div className="color-gray-medium">Copied</div>}
</div>
)
);
}
export default SessionCopyLink
const SessionCopyLinkCompo = connectPlayer((state: any) => ({
time: state.time,
}))(SessionCopyLink);
export default React.memo(SessionCopyLinkCompo);

View file

@ -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 (
<Tooltip
open={isOpen}
theme="light"
interactive
position="bottom"
unmountHTMLWhenHide
useContext
arrow
trigger="click"
shown={this.handleOpen}
// beforeHidden={this.handleClose}
html={
<OutsideClickDetectingDiv
className={cn('relative flex items-center')}
onClickOutside={() => {
this.setState({ isOpen: false })
}}
>
<OutsideClickDetectingDiv
className={cn('relative flex items-center')}
onClickOutside={() => {
this.setState({ isOpen: false });
}}
>
<Tooltip
open={isOpen}
theme="light"
interactive
position="bottom"
unmountHTMLWhenHide
useContext
arrow
trigger="click"
shown={this.handleOpen}
// beforeHidden={this.handleClose}
html={
<div className={styles.wrapper}>
<div className={styles.header}>
<div className={cn(styles.title, 'text-lg')}>Share this session link to Slack</div>
@ -105,7 +105,7 @@ export default class SharePopup extends React.PureComponent {
</div>
{showCopyLink && (
<div className={styles.footer}>
<SessionCopyLink time={time} />
<SessionCopyLink />
</div>
)}
</>
@ -132,7 +132,7 @@ export default class SharePopup extends React.PureComponent {
className="mr-4"
/>
<div>
<Button onClick={this.share} primary>
<Button onClick={this.share} variant="primary">
<div className="flex items-center">
<Icon name="integrations/slack-bw" size="18" marginRight="10" />
{loading ? 'Sending...' : 'Send'}
@ -142,16 +142,16 @@ export default class SharePopup extends React.PureComponent {
</div>
</div>
<div className={styles.footer}>
<SessionCopyLink time={time} />
<SessionCopyLink />
</div>
</div>
)}
</div>
</OutsideClickDetectingDiv>
}
>
<span onClick={this.onClickHandler}>{trigger}</span>
</Tooltip>
}
>
<div onClick={this.onClickHandler}>{trigger}</div>
</Tooltip>
</OutsideClickDetectingDiv>
);
}
}

View file

@ -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 (
<div>
{/* {cloneElement(children, getReferenceProps({ ref, ...children.props }))} */}
<button ref={reference} {...getReferenceProps({ ref, ...children.props })}>Button</button>
{open && (
<div
ref={floating}
className="Tooltip"
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
}}
{...getFloatingProps()}
>
{label}
</div>
)}
</div>
);
}
export default AnimatedTooltip;

View file

@ -0,0 +1 @@
export { default } from './AnimatedTooltip';

View file

@ -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';