change(ui) - notes guide
This commit is contained in:
parent
c73cb9e60a
commit
55bbd85972
6 changed files with 167 additions and 50 deletions
|
|
@ -3,6 +3,7 @@ import { Button } from 'UI';
|
|||
import { connectPlayer, pause } from 'Player';
|
||||
import { connect } from 'react-redux';
|
||||
import { setCreateNoteTooltip } from 'Duck/sessions';
|
||||
import GuidePopup, { FEATURE_KEYS } from 'Shared/GuidePopup';
|
||||
|
||||
function NotePopup({
|
||||
setCreateNoteTooltip,
|
||||
|
|
@ -24,9 +25,18 @@ function NotePopup({
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<Button icon="quotes" variant="text" disabled={tooltipActive} onClick={toggleNotePopup}>
|
||||
Add Note
|
||||
</Button>
|
||||
<GuidePopup
|
||||
title={
|
||||
<div className="color-gray-dark">
|
||||
Introducing <span className={''}>Notes</span>
|
||||
</div>
|
||||
}
|
||||
description={'Annotate session replays and share your feedback with the rest of your team.'}
|
||||
>
|
||||
<Button icon="quotes" variant="text" disabled={tooltipActive} onClick={toggleNotePopup}>
|
||||
Add Note
|
||||
</Button>
|
||||
</GuidePopup>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +46,7 @@ const NotePopupPl = connectPlayer(
|
|||
)(React.memo(NotePopup));
|
||||
|
||||
const NotePopupComp = connect(
|
||||
(state) => ({ tooltipActive: state.getIn(['sessions', 'createNoteTooltip', 'isVisible']) }),
|
||||
(state: any) => ({ tooltipActive: state.getIn(['sessions', 'createNoteTooltip', 'isVisible']) }),
|
||||
{ setCreateNoteTooltip }
|
||||
)(NotePopupPl);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,71 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Controls as Player } from 'Player';
|
||||
import { Tooltip } from 'UI';
|
||||
import { INDEXES } from 'App/constants/zindex';
|
||||
|
||||
export const FEATURE_KEYS = {
|
||||
XRAY: 'featureViewed'
|
||||
}
|
||||
XRAY: 'featureViewed',
|
||||
NOTES: 'notesFeatureViewed',
|
||||
};
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactNode
|
||||
title: React.ReactNode
|
||||
description: React.ReactNode
|
||||
key?: keyof typeof FEATURE_KEYS
|
||||
children?: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
key?: keyof typeof FEATURE_KEYS;
|
||||
}
|
||||
|
||||
export default function GuidePopup({ children, title, description }: IProps) {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
<div className="font-bold">
|
||||
{title}
|
||||
const [showGuide, setShowGuide] = useState(!localStorage.getItem(FEATURE_KEYS.NOTES));
|
||||
useEffect(() => {
|
||||
if (!showGuide) {
|
||||
return;
|
||||
}
|
||||
Player.pause();
|
||||
}, []);
|
||||
|
||||
const onClick = () => {
|
||||
setShowGuide(false);
|
||||
localStorage.setItem(FEATURE_KEYS.NOTES, 'true');
|
||||
};
|
||||
|
||||
return showGuide ? (
|
||||
<div>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="bg-gray-darkest fixed inset-0 z-10 w-full h-screen cursor-pointer"
|
||||
style={{ zIndex: INDEXES.POPUP_GUIDE_BG, opacity: '0.7' }}
|
||||
></div>
|
||||
<Tooltip
|
||||
offset={30}
|
||||
className="!bg-white rounded text-center shadow !p-6"
|
||||
title={
|
||||
<div className="relative">
|
||||
<div className="font-bold">{title}</div>
|
||||
<div className="color-gray-medium w-80">{description}</div>
|
||||
<div className="w-10 h-10 bg-white rotate-45 absolute right-0 left-0 m-auto" style={{ top: '-38px'}} />
|
||||
</div>
|
||||
<div className="color-gray-medium">
|
||||
{description}
|
||||
}
|
||||
open={true}
|
||||
>
|
||||
<div className="relative pointer-events-none">
|
||||
<div className="" style={{ zIndex: INDEXES.POPUP_GUIDE_BTN, position: 'inherit' }}>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
className="absolute bg-white top-0 left-0"
|
||||
style={{
|
||||
zIndex: INDEXES.POPUP_GUIDE_BG,
|
||||
width: '120px',
|
||||
height: '40px',
|
||||
borderRadius: '30px',
|
||||
margin: '-2px -10px',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
// distance={30}
|
||||
// theme={'light'}
|
||||
open={true}
|
||||
// arrow={true}
|
||||
>
|
||||
{children}
|
||||
</Tooltip>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ function XRayButton(props: Props) {
|
|||
)}
|
||||
<div className="relative">
|
||||
{showGuide ? (
|
||||
<GuidePopup
|
||||
title={<>Introducing <span className={stl.text}>X-Ray</span></>}
|
||||
description={"Get a quick overview on the issues in this session."}
|
||||
>
|
||||
// <GuidePopup
|
||||
// title={<div className="color-gray-dark">Introducing <span className={stl.text}>X-Ray</span></div>}
|
||||
// description={"Get a quick overview on the issues in this session."}
|
||||
// >
|
||||
<button
|
||||
className={cn(stl.wrapper, { [stl.default]: !isActive, [stl.active]: isActive })}
|
||||
onClick={onClick}
|
||||
|
|
@ -51,17 +51,17 @@ function XRayButton(props: Props) {
|
|||
<span className="z-1">X-RAY</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className="absolute bg-white top-0 left-0 z-0"
|
||||
style={{
|
||||
zIndex: INDEXES.POPUP_GUIDE_BG,
|
||||
width: '100px',
|
||||
height: '50px',
|
||||
borderRadius: '30px',
|
||||
margin: '-10px -16px',
|
||||
}}
|
||||
></div>
|
||||
</GuidePopup>
|
||||
// <div
|
||||
// className="absolute bg-white top-0 left-0 z-0"
|
||||
// style={{
|
||||
// zIndex: INDEXES.POPUP_GUIDE_BG,
|
||||
// width: '100px',
|
||||
// height: '50px',
|
||||
// borderRadius: '30px',
|
||||
// margin: '-10px -16px',
|
||||
// }}
|
||||
// ></div>
|
||||
// </GuidePopup>
|
||||
) : (
|
||||
<Tooltip title="Get a quick overview on the issues in this session." disabled={isActive}>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { mergeRefs } from 'react-merge-refs';
|
|||
import {
|
||||
useFloating,
|
||||
autoUpdate,
|
||||
offset,
|
||||
offset as _offset,
|
||||
flip,
|
||||
shift,
|
||||
useHover,
|
||||
|
|
@ -12,6 +12,8 @@ import {
|
|||
useRole,
|
||||
useInteractions,
|
||||
FloatingPortal,
|
||||
arrow,
|
||||
computePosition,
|
||||
} from '@floating-ui/react-dom-interactions';
|
||||
import type { Placement } from '@floating-ui/react-dom-interactions';
|
||||
import { INDEXES } from 'App/constants/zindex';
|
||||
|
|
@ -20,14 +22,19 @@ export function useTooltipState({
|
|||
disabled = false,
|
||||
initialOpen = false,
|
||||
placement = 'top',
|
||||
offset = 5,
|
||||
delay,
|
||||
arrowRef = null,
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
initialOpen?: boolean;
|
||||
placement?: Placement;
|
||||
offset?: number;
|
||||
delay?: number;
|
||||
arrowRef?: any;
|
||||
} = {}) {
|
||||
const [open, setOpen] = React.useState(initialOpen);
|
||||
const staticSide = mapPlacementSideToCSSProperty(placement);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
@ -40,7 +47,7 @@ export function useTooltipState({
|
|||
open,
|
||||
onOpenChange: setOpen,
|
||||
whileElementsMounted: autoUpdate,
|
||||
middleware: [offset(5), flip(), shift()],
|
||||
middleware: [_offset(offset), flip(), shift(), arrow({ element: arrowRef })],
|
||||
});
|
||||
|
||||
const context = data.context;
|
||||
|
|
@ -49,15 +56,16 @@ export function useTooltipState({
|
|||
const focus = useFocus(context);
|
||||
const dismiss = useDismiss(context);
|
||||
const role = useRole(context, { role: 'tooltip' });
|
||||
|
||||
const interactions = useInteractions([hover, focus, dismiss, role]);
|
||||
|
||||
return React.useMemo(
|
||||
() => ({
|
||||
open,
|
||||
setOpen,
|
||||
arrowRef,
|
||||
...interactions,
|
||||
...data,
|
||||
staticSide,
|
||||
}),
|
||||
[open, setOpen, interactions, data]
|
||||
);
|
||||
|
|
@ -73,6 +81,7 @@ export const TooltipAnchor = React.forwardRef<
|
|||
}
|
||||
>(function TooltipAnchor({ children, state, asChild = false, ...props }, propRef) {
|
||||
const childrenRef = (children as any).ref;
|
||||
|
||||
const ref = React.useMemo(
|
||||
() => mergeRefs([state.reference, propRef, childrenRef]),
|
||||
[state.reference, propRef, childrenRef]
|
||||
|
|
@ -119,3 +128,46 @@ export const FloatingTooltip = React.forwardRef<
|
|||
</FloatingPortal>
|
||||
);
|
||||
});
|
||||
|
||||
function mapPlacementSideToCSSProperty(placement: Placement) {
|
||||
const staticSide = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[placement.split('-')[0]];
|
||||
|
||||
return staticSide;
|
||||
}
|
||||
|
||||
export const FloatingArrow = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLProps<HTMLDivElement> & { state: TooltipState }
|
||||
>(function Tooltip({ state, ...props }, propRef) {
|
||||
const ref = React.useMemo(() => state.arrowRef, [state.arrowRef, propRef]);
|
||||
const { x: arrowX, y: arrowY } = state.middlewareData?.arrow || { x: 0, y: 0 };
|
||||
const staticSide = state.staticSide;
|
||||
|
||||
return (
|
||||
<FloatingPortal>
|
||||
{state.open && (
|
||||
<div
|
||||
ref={ref}
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
backgroundColor: 'white',
|
||||
position: state.strategy,
|
||||
left: arrowX != null ? `${arrowX + state.x}px` : '',
|
||||
top: arrowY != null ? `${arrowY + state.y}px` : '',
|
||||
[staticSide]: '-10px',
|
||||
zIndex: INDEXES.TOOLTIP - 1,
|
||||
transform: 'rotate(45deg)',
|
||||
...props.style,
|
||||
}}
|
||||
{...state.getFloatingProps(props)}
|
||||
/>
|
||||
)}
|
||||
</FloatingPortal>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTooltipState, TooltipAnchor, FloatingTooltip } from './FloatingTooltip';
|
||||
import { useTooltipState, TooltipAnchor, FloatingTooltip, FloatingArrow } from './FloatingTooltip';
|
||||
import type { Placement } from '@floating-ui/react-dom-interactions';
|
||||
import cn from 'classnames';
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ interface Props {
|
|||
className?: string;
|
||||
delay?: number;
|
||||
style?: any;
|
||||
offset?: number;
|
||||
}
|
||||
function Tooltip(props: Props) {
|
||||
const {
|
||||
|
|
@ -22,18 +23,30 @@ function Tooltip(props: Props) {
|
|||
className = '',
|
||||
delay = 500,
|
||||
style = {},
|
||||
offset = 5,
|
||||
} = props;
|
||||
const state = useTooltipState({ disabled: disabled, placement, delay });
|
||||
const arrowRef = React.useRef(null);
|
||||
|
||||
const state = useTooltipState({
|
||||
disabled: disabled,
|
||||
placement,
|
||||
delay,
|
||||
initialOpen: open,
|
||||
offset,
|
||||
arrowRef,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<TooltipAnchor state={state}>{props.children}</TooltipAnchor>
|
||||
<FloatingTooltip
|
||||
state={state}
|
||||
className={cn('bg-gray-darkest color-white rounded py-1 px-2 animate-fade', className)}
|
||||
>
|
||||
{title}
|
||||
{/* <FloatingArrow state={state} className="" /> */}
|
||||
</FloatingTooltip>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -347,4 +347,12 @@ p {
|
|||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#arrow {
|
||||
position: absolute;
|
||||
background: #333;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue