Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/demo/inline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: inline
nav:
title: Demo
path: /demo
---

<code src="../examples/inline.tsx"></code>
59 changes: 59 additions & 0 deletions docs/examples/inline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useRef } from 'react';
import Tour from '../../src/index';
import './basic.less';

const App = () => {
const [open, setOpen] = React.useState(true);
const createBtnRef = useRef<HTMLButtonElement>(null);

return (
<div style={{ margin: 20 }}>
<div
style={{
width: 800,
height: 500,
boxShadow: '0 0 0 1px red',
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'center',
// justifyContent: 'right',
position: 'relative',
overflow: 'hidden',
}}
id="inlineHolder"
>
<button
className="ant-target"
ref={createBtnRef}
onClick={() => {
setOpen(true);
}}
>
Show
</button>

<Tour
open={open}
defaultCurrent={0}
getPopupContainer={false}
onClose={() => {
setOpen(false);
}}
// style={{ background: 'red' }}
steps={[
{
title: '创建',
description: '创建一条数据',
target: () => createBtnRef.current,
mask: true,
},
]}
/>
</div>

<div style={{ height: '200vh' }} />
</div>
);
};

export default App;
43 changes: 32 additions & 11 deletions src/Mask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Portal from '@rc-component/portal';
import type { PosInfo } from './hooks/useTarget';
import useId from 'rc-util/lib/hooks/useId';
import { SemanticName } from './interface';
import type { SemanticName, TourProps } from './interface';

const COVER_PROPS = {
fill: 'transparent',
Expand All @@ -24,6 +24,7 @@
disabledInteraction?: boolean;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
getPopupContainer?: TourProps['getPopupContainer'];
}

const Mask = (props: MaskProps) => {
Expand All @@ -33,29 +34,44 @@
pos,
showMask,
style = {},
fill = "rgba(0,0,0,0.5)",
fill = 'rgba(0,0,0,0.5)',
open,
animated,
zIndex,
disabledInteraction,
styles,
classNames: tourClassNames,
getPopupContainer,
} = props;

const id = useId();
const maskId = `${prefixCls}-mask-${id}`;
const mergedAnimated =
typeof animated === 'object' ? animated?.placeholder : animated;

const isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const maskRectSize = isSafari ? { width: '100%', height: '100%' } : { width: '100vw', height: '100vh'};
const isSafari =
typeof navigator !== 'undefined' &&
/^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const maskRectSize = isSafari
? { width: '100%', height: '100%' }

Check warning on line 56 in src/Mask.tsx

View check run for this annotation

Codecov / codecov/patch

src/Mask.tsx#L56

Added line #L56 was not covered by tests
: { width: '100vw', height: '100vh' };

const inlineMode = getPopupContainer === false;

return (
<Portal open={open} autoLock>
<Portal
open={open}
autoLock={!inlineMode}
getContainer={getPopupContainer as any}
>
<div
className={classNames(`${prefixCls}-mask`, rootClassName, tourClassNames?.mask)}
className={classNames(
`${prefixCls}-mask`,
rootClassName,
tourClassNames?.mask,
)}
style={{
position: 'fixed',
position: inlineMode ? 'absolute' : 'fixed',
left: 0,
right: 0,
top: 0,
Expand Down Expand Up @@ -103,32 +119,37 @@
{/* Block click region */}
{pos && (
<>
{/* Top */}

<rect
{...COVER_PROPS}
x="0"
y="0"
width="100%"
height={pos.top}
height={Math.max(pos.top, 0)}
/>
{/* Left */}
<rect
{...COVER_PROPS}
x="0"
y="0"
width={pos.left}
width={Math.max(pos.left, 0)}
height="100%"
/>
{/* Bottom */}
<rect
{...COVER_PROPS}
x="0"
y={pos.top + pos.height}
width="100%"
height={`calc(100vh - ${pos.top + pos.height}px)`}
height={`calc(100% - ${pos.top + pos.height}px)`}
/>
{/* Right */}
<rect
{...COVER_PROPS}
x={pos.left + pos.width}
y="0"
width={`calc(100vw - ${pos.left + pos.width}px)`}
width={`calc(100% - ${pos.left + pos.width}px)`}
height="100%"
/>
</>
Expand Down
24 changes: 21 additions & 3 deletions src/Tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const Tour: React.FC<TourProps> = props => {
onClose,
onFinish,
open,
defaultOpen,
mask = true,
arrow = true,
rootClassName,
Expand All @@ -55,6 +56,7 @@ const Tour: React.FC<TourProps> = props => {
classNames: tourClassNames,
className,
style,
getPopupContainer,
...restProps
} = props;

Expand All @@ -65,7 +67,7 @@ const Tour: React.FC<TourProps> = props => {
defaultValue: defaultCurrent,
});

const [mergedOpen, setMergedOpen] = useMergedState(undefined, {
const [mergedOpen, setMergedOpen] = useMergedState(defaultOpen, {
value: open,
postState: origin =>
mergedCurrent < 0 || mergedCurrent >= steps.length
Expand Down Expand Up @@ -112,11 +114,19 @@ const Tour: React.FC<TourProps> = props => {
const mergedMask = mergedOpen && (stepMask ?? mask);
const mergedScrollIntoViewOptions =
stepScrollIntoViewOptions ?? scrollIntoViewOptions;

// ====================== Align Target ======================
const placeholderRef = React.useRef<HTMLDivElement>(null);

const inlineMode = getPopupContainer === false;

const [posInfo, targetElement] = useTarget(
target,
open,
gap,
mergedScrollIntoViewOptions,
inlineMode,
placeholderRef,
);
const mergedPlacement = getPlacement(targetElement, placement, stepPlacement);

Expand Down Expand Up @@ -198,6 +208,7 @@ const Tour: React.FC<TourProps> = props => {
return (
<>
<Mask
getPopupContainer={getPopupContainer}
styles={styles}
classNames={tourClassNames}
zIndex={zIndex}
Expand All @@ -213,6 +224,8 @@ const Tour: React.FC<TourProps> = props => {
/>
<Trigger
{...restProps}
// `rc-portal` def bug not support `false` but does support and in used.
getPopupContainer={getPopupContainer as any}
builtinPlacements={mergedBuiltinPlacements}
ref={triggerRef}
popupStyle={stepStyle}
Expand All @@ -227,16 +240,21 @@ const Tour: React.FC<TourProps> = props => {
getTriggerDOMNode={getTriggerDOMNode}
arrow={!!mergedArrow}
>
<Portal open={mergedOpen} autoLock>
<Portal
open={mergedOpen}
autoLock={!inlineMode}
getContainer={getPopupContainer as any}
>
<div
ref={placeholderRef}
className={classNames(
className,
rootClassName,
`${prefixCls}-target-placeholder`,
)}
style={{
...(posInfo || CENTER_PLACEHOLDER),
position: 'fixed',
position: inlineMode ? 'absolute' : 'fixed',
pointerEvents: 'none',
...style,
}}
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export default function useTarget(
open: boolean,
gap?: Gap,
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions,
inlineMode?: boolean,
placeholderRef?: React.RefObject<HTMLDivElement>,
): [PosInfo, HTMLElement] {
// ========================= Target =========================
// We trade `undefined` as not get target by function yet.
Expand Down Expand Up @@ -53,6 +55,17 @@ export default function useTarget(
targetElement.getBoundingClientRect();
const nextPosInfo: PosInfo = { left, top, width, height, radius: 0 };

// If `inlineMode` we need cut off parent offset
if (inlineMode) {
const parentRect =
placeholderRef.current?.parentElement?.getBoundingClientRect();

if (parentRect) {
nextPosInfo.left -= parentRect.left;
nextPosInfo.top -= parentRect.top;
}
}

setPosInfo(origin => {
if (JSON.stringify(origin) !== JSON.stringify(nextPosInfo)) {
return nextPosInfo;
Expand Down
3 changes: 2 additions & 1 deletion src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface TourProps extends Pick<TriggerProps, 'onPopupAlign'> {
style?: React.CSSProperties;
steps?: TourStepInfo[];
open?: boolean;
defaultOpen?: boolean;
defaultCurrent?: number;
current?: number;
onChange?: (current: number) => void;
Expand All @@ -76,7 +77,7 @@ export interface TourProps extends Pick<TriggerProps, 'onPopupAlign'> {
animated?: boolean | { placeholder: boolean };
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions;
zIndex?: number;
getPopupContainer?: TriggerProps['getPopupContainer'];
getPopupContainer?: TriggerProps['getPopupContainer'] | false;
builtinPlacements?:
| TriggerProps['builtinPlacements']
| ((config?: {
Expand Down
Loading
Loading