Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/demo/gap.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ nav:
## Radius

<code src="../examples/gap-radius.tsx"></code>

## Offset

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

const App = () => {
const button1Ref = useRef<HTMLButtonElement>(null);
const button2Ref = useRef<HTMLButtonElement>(null);
const button3Ref = useRef<HTMLButtonElement>(null);

const [open, setOpen] = useState(false);
const offset: [number, number][] = [
[10, 10],
[20, 20],
[30, 30],
]
return (
<div style={{ margin: 20 }}>
<div style={{ display: 'flex', gap: 10 }}>
<button onClick={() => setOpen(true)}>Open</button>
<button ref={button1Ref}>button 1</button>
<button ref={button2Ref}>button 2</button>
<button ref={button3Ref}>button 3</button>
</div>


<div style={{ height: 200 }} />
<Tour
open={open}
gap={{
offset
}}
steps={[
{
title: '创建1',
description: '创建一条数据1',
target: () => button1Ref.current,
mask: true,
},
{
title: '创建2',
description: '创建一条数据2',
target: () => button2Ref.current,
mask: true,
},
{
title: '创建3',
description: '创建一条数据3',
target: () => button3Ref.current,
mask: true,
},
]}
onClose={() => {
setOpen(false);
}}
/>
</div>
);
};

export default App;
3 changes: 2 additions & 1 deletion src/Tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const Tour: React.FC<TourProps> = props => {
mergedScrollIntoViewOptions,
inlineMode,
placeholderRef,
mergedCurrent
);
const mergedPlacement = getPlacement(targetElement, placement, stepPlacement);

Expand Down Expand Up @@ -263,4 +264,4 @@ const Tour: React.FC<TourProps> = props => {
);
};

export default Tour;
export default Tour;
30 changes: 26 additions & 4 deletions src/hooks/useTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { TourStepInfo } from '..';
import { isInViewPort } from '../util';

export interface Gap {
offset?: number | [number, number];
offset?: number | [number, number] | [number, number][];
radius?: number;
}

Expand All @@ -16,6 +16,8 @@ export interface PosInfo {
width: number;
radius: number;
}
const DEFAULT_GAP_OFFSET = 6;

function isValidNumber(val) {
return typeof val === 'number' && !Number.isNaN(val);
}
Expand All @@ -27,6 +29,7 @@ export default function useTarget(
scrollIntoViewOptions?: boolean | ScrollIntoViewOptions,
inlineMode?: boolean,
placeholderRef?: React.RefObject<HTMLDivElement>,
current: number = 0,
): [PosInfo, HTMLElement] {
// ========================= Target =========================
// We trade `undefined` as not get target by function yet.
Expand Down Expand Up @@ -78,8 +81,27 @@ export default function useTarget(
}
});

const getGapOffset = (index: number) =>
(Array.isArray(gap?.offset) ? gap?.offset[index] : gap?.offset) ?? 6;
const getGapOffset = (index: number): number => {
if (gap?.offset === undefined) return DEFAULT_GAP_OFFSET;

if (typeof gap.offset === 'number') {
return gap.offset;
}

if (Array.isArray(gap.offset)) {
if (typeof gap.offset[0] === 'number') {
const tuple = gap.offset as [number, number];
return tuple[index] ?? DEFAULT_GAP_OFFSET;
}
if (Array.isArray(gap.offset[0])) {
const arrayOfTuples = gap.offset as [number, number][];
const stepIndex = current ?? 0;
return arrayOfTuples[stepIndex]?.[index] ?? DEFAULT_GAP_OFFSET;
}
}

return DEFAULT_GAP_OFFSET;
};

useLayoutEffect(() => {
updatePos();
Expand Down Expand Up @@ -111,7 +133,7 @@ export default function useTarget(
height: posInfo.height + gapOffsetY * 2,
radius: gapRadius,
};
}, [posInfo, gap]);
}, [posInfo, gap, current]);

return [mergedPosInfo, targetElement];
}
82 changes: 82 additions & 0 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1289,4 +1289,86 @@ describe('Tour', () => {
height: 0,
});
});

it('should support gap.offset with array<[number, number]> and change on next step', async () => {
mockBtnRect({
x: 100,
y: 100,
width: 230,
height: 180,
});

const Demo = () => {
const button1Ref = useRef<HTMLButtonElement>(null);
const button2Ref = useRef<HTMLButtonElement>(null);
const button3Ref = useRef<HTMLButtonElement>(null);

return (
<div style={{ margin: 20 }}>
<button ref={button1Ref}>button 1</button>
<button ref={button2Ref}>button 2</button>
<button ref={button3Ref}>button 3</button>
<Tour
open
gap={{
offset: [[10, 15], [20, 25], [30, 35]] as [number, number][],
}}
steps={[
{
title: 'step 1',
description: 'description 1',
target: () => button1Ref.current,
},
{
title: 'step 2',
description: 'description 2',
target: () => button2Ref.current,
},
{
title: 'step 3',
description: 'description 3',
target: () => button3Ref.current,
},
]}
/>
</div>
);
};

render(<Demo />);
await act(() => {
jest.runAllTimers();
});

// gap [10, 15] -> width: 230 + 20 = 250, height: 180 + 30 = 210
let targetRect = document
.getElementById('rc-tour-mask-test-id')
.querySelectorAll('rect')[1];
expect(targetRect).toHaveAttribute('width', '250');
expect(targetRect).toHaveAttribute('height', '210');

fireEvent.click(screen.getByRole('button', { name: 'Next' }));
await act(() => {
jest.runAllTimers();
});

// gap [20, 25] -> width: 230 + 40 = 270, height: 180 + 50 = 230
targetRect = document
.getElementById('rc-tour-mask-test-id')
.querySelectorAll('rect')[1];
expect(targetRect).toHaveAttribute('width', '270');
expect(targetRect).toHaveAttribute('height', '230');

fireEvent.click(screen.getByRole('button', { name: 'Next' }));
await act(() => {
jest.runAllTimers();
});

// gap [30, 35] -> width: 230 + 60 = 290, height: 180 + 70 = 250
targetRect = document
.getElementById('rc-tour-mask-test-id')
.querySelectorAll('rect')[1];
expect(targetRect).toHaveAttribute('width', '290');
expect(targetRect).toHaveAttribute('height', '250');
});
});