diff --git a/docs/examples/basic.less b/docs/examples/basic.less index 550a397..dd3cce5 100644 --- a/docs/examples/basic.less +++ b/docs/examples/basic.less @@ -214,6 +214,7 @@ background: #000; margin-right:4px; opacity: 0.75; + cursor: pointer; &.active{ background: #007aff; } @@ -223,6 +224,7 @@ &-prev-btn,&-next-btn,&-finish-btn{ display: inline-block; margin-left: 8px; + cursor: pointer; } .ant-btn-primary { diff --git a/src/Tour.tsx b/src/Tour.tsx index 0a49e6a..3b1ebda 100644 --- a/src/Tour.tsx +++ b/src/Tour.tsx @@ -170,6 +170,9 @@ const Tour: React.FC = props => { }} {...steps[mergedCurrent]} closable={mergedClosable} + onClickSlider={(index: number) => { + onInternalChange(index); + }} /> ); diff --git a/src/TourStep/DefaultPanel.tsx b/src/TourStep/DefaultPanel.tsx index dba94f8..4640b2b 100644 --- a/src/TourStep/DefaultPanel.tsx +++ b/src/TourStep/DefaultPanel.tsx @@ -3,8 +3,8 @@ import type { TourStepProps } from '../interface'; import classNames from 'classnames'; import pickAttrs from 'rc-util/lib/pickAttrs'; -export type DefaultPanelProps = Exclude & { - closable: Exclude; +export type DefaultPanelProps = Exclude & { + closable: Exclude; }; export default function DefaultPanel(props: DefaultPanelProps) { @@ -20,11 +20,18 @@ export default function DefaultPanel(props: DefaultPanelProps) { onFinish, className, closable, + onClickSlider, } = props; const ariaProps = pickAttrs(closable || {}, true); const closeIcon = closable?.closeIcon; const mergedClosable = !!closable; + const handleClickSlider = (index: number, currentIndex: number) => { + if (index !== currentIndex) { + onClickSlider(index); + } + }; + return (
@@ -47,13 +54,14 @@ export default function DefaultPanel(props: DefaultPanelProps) {
{total > 1 ? [...Array.from({ length: total }).keys()].map((item, index) => { - return ( - - ); - }) + return ( + handleClickSlider(index, current)} + /> + ); + }) : null}
diff --git a/src/interface.ts b/src/interface.ts index adbdd30..c97dfde 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -1,70 +1,71 @@ -import type { TriggerProps } from "@rc-component/trigger"; -import type { PlacementType } from "./placements"; -import type { CSSProperties, ReactNode } from "react"; -import type { Gap } from "./hooks/useTarget"; - +import type { TriggerProps } from '@rc-component/trigger'; +import type { PlacementType } from './placements'; +import type { CSSProperties, ReactNode } from 'react'; +import type { Gap } from './hooks/useTarget'; export interface TourStepInfo { - arrow?: boolean | { pointAtCenter: boolean }; - target?: HTMLElement | (() => HTMLElement) | null | (() => null); - title: ReactNode; - description?: ReactNode; - placement?: PlacementType; - mask?: boolean | { - style?: React.CSSProperties; - // to fill mask color, e.g. rgba(80,0,0,0.5) - color?: string; - }; - className?: string; - style?: CSSProperties; - scrollIntoViewOptions?: boolean | ScrollIntoViewOptions; - closeIcon?: ReactNode; - closable?: boolean | ({ closeIcon?: ReactNode } & React.AriaAttributes); - } - - export interface TourStepProps extends TourStepInfo { - prefixCls?: string; - total?: number; - current?: number; - onClose?: () => void; - onFinish?: () => void; - renderPanel?: (step: TourStepProps, current: number) => ReactNode; - onPrev?: () => void; - onNext?: () => void; - } - + arrow?: boolean | { pointAtCenter: boolean }; + target?: HTMLElement | (() => HTMLElement) | null | (() => null); + title: ReactNode; + description?: ReactNode; + placement?: PlacementType; + mask?: + | boolean + | { + style?: React.CSSProperties; + // to fill mask color, e.g. rgba(80,0,0,0.5) + color?: string; + }; + className?: string; + style?: CSSProperties; + scrollIntoViewOptions?: boolean | ScrollIntoViewOptions; + closeIcon?: ReactNode; + closable?: boolean | ({ closeIcon?: ReactNode } & React.AriaAttributes); +} + +export interface TourStepProps extends TourStepInfo { + prefixCls?: string; + total?: number; + current?: number; + onClose?: () => void; + onFinish?: () => void; + renderPanel?: (step: TourStepProps, current: number) => ReactNode; + onPrev?: () => void; + onNext?: () => void; + onClickSlider?: (index: number) => void; +} export interface TourProps extends Pick { - steps?: TourStepInfo[]; - open?: boolean; - defaultCurrent?: number; - current?: number; - onChange?: (current: number) => void; - onClose?: (current: number) => void; - onFinish?: () => void; - closeIcon?: TourStepProps['closeIcon']; - closable?: TourStepProps['closable']; - mask?: - | boolean - | { - style?: React.CSSProperties; - // to fill mask color, e.g. rgba(80,0,0,0.5) - color?: string; - }; - arrow?: boolean | { pointAtCenter: boolean }; - rootClassName?: string; - placement?: PlacementType; - prefixCls?: string; - renderPanel?: (props: TourStepProps, current: number) => ReactNode; - gap?: Gap; - animated?: boolean | { placeholder: boolean }; - scrollIntoViewOptions?: boolean | ScrollIntoViewOptions; - zIndex?: number; - getPopupContainer?: TriggerProps['getPopupContainer']; - builtinPlacements?: - | TriggerProps['builtinPlacements'] - | ((config?: { - arrowPointAtCenter?: boolean; - }) => TriggerProps['builtinPlacements']); - disabledInteraction?: boolean; - } \ No newline at end of file + steps?: TourStepInfo[]; + open?: boolean; + defaultCurrent?: number; + current?: number; + onChange?: (current: number) => void; + onClose?: (current: number) => void; + onFinish?: () => void; + closeIcon?: TourStepProps['closeIcon']; + closable?: TourStepProps['closable']; + mask?: + | boolean + | { + style?: React.CSSProperties; + // to fill mask color, e.g. rgba(80,0,0,0.5) + color?: string; + }; + arrow?: boolean | { pointAtCenter: boolean }; + rootClassName?: string; + placement?: PlacementType; + prefixCls?: string; + renderPanel?: (props: TourStepProps, current: number) => ReactNode; + gap?: Gap; + animated?: boolean | { placeholder: boolean }; + scrollIntoViewOptions?: boolean | ScrollIntoViewOptions; + zIndex?: number; + getPopupContainer?: TriggerProps['getPopupContainer']; + builtinPlacements?: + | TriggerProps['builtinPlacements'] + | ((config?: { + arrowPointAtCenter?: boolean; + }) => TriggerProps['builtinPlacements']); + disabledInteraction?: boolean; +} \ No newline at end of file diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 4b130c4..68a4861 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -8,6 +8,7 @@ import Tour from '../src/index'; import { getPlacements, placements } from '../src/placements'; import { getPlacement } from '../src/util'; import { resizeWindow } from './utils'; +import DefaultPanel from '../src/TourStep/DefaultPanel'; const mockBtnRect = ( rect: { @@ -225,6 +226,27 @@ describe('Tour', () => { ).toBeTruthy(); expect(baseElement).toMatchSnapshot(); }); + + it('click-slider', () => { + const onClickSliderMock = jest.fn(); + const total = 3; + const current = 1; // Assuming current index is 1 + + const { container } = render( + , + ); + + const sliderButtons = container.querySelectorAll('.rc-tour-sliders span'); + + fireEvent.click(sliderButtons[current]); + + expect(onClickSliderMock).not.toHaveBeenCalled(); + }); }); it('rootClassName', async () => { @@ -680,7 +702,9 @@ describe('Tour', () => { animated={false} open={open} placement={'bottom'} - builtinPlacements={config => getPlacements(config.arrowPointAtCenter)} + builtinPlacements={config => + getPlacements(config.arrowPointAtCenter) + } steps={[ { title: '创建', @@ -890,7 +914,11 @@ describe('Tour', () => { }); it('support closable', () => { - const Demo = ({ closable = false }: { closable?: TourProps["closable"] }) => { + const Demo = ({ + closable = false, + }: { + closable?: TourProps['closable']; + }) => { const createBtnRef = useRef(null); const updateBtnRef = useRef(null); const deleteBtnRef = useRef(null); @@ -927,8 +955,10 @@ describe('Tour', () => { { title: '删除', closable: { - closeIcon: Close, - "aria-label": "关闭", + closeIcon: ( + Close + ), + 'aria-label': '关闭', }, description: (
@@ -959,7 +989,9 @@ describe('Tour', () => { expect(baseElement.querySelector('.rc-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.rc-tour-close-x')).toBeFalsy(); expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy(); - expect(baseElement.querySelector('.rc-tour-close').getAttribute("aria-label")).toBe("关闭"); + expect( + baseElement.querySelector('.rc-tour-close').getAttribute('aria-label'), + ).toBe('关闭'); resetIndex(); @@ -973,19 +1005,25 @@ describe('Tour', () => { expect(baseElement.querySelector('.rc-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.rc-tour-close-x')).toBeFalsy(); expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy(); - expect(baseElement.querySelector('.rc-tour-close').getAttribute("aria-label")).toBe("关闭"); + expect( + baseElement.querySelector('.rc-tour-close').getAttribute('aria-label'), + ).toBe('关闭'); resetIndex(); rerender( - X, - "aria-label": "关闭", - }} />, + X, + 'aria-label': '关闭', + }} + />, ); expect(baseElement.querySelector('.rc-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.custom-global-close-icon')).toBeTruthy(); - expect(baseElement.querySelector('.rc-tour-close').getAttribute("aria-label")).toBe("关闭"); + expect( + baseElement.querySelector('.rc-tour-close').getAttribute('aria-label'), + ).toBe('关闭'); fireEvent.click(screen.getByRole('button', { name: 'Next' })); expect(baseElement.querySelector('.rc-tour-close')).toBeFalsy(); expect(baseElement.querySelector('.rc-tour-close-x')).toBeFalsy(); @@ -1070,6 +1108,8 @@ describe('Tour', () => { render(); - expect(document.querySelector('.rc-tour-mask')).toHaveStyle('pointer-events: auto') - }) + expect(document.querySelector('.rc-tour-mask')).toHaveStyle( + 'pointer-events: auto', + ); + }); });