Browse Source

fix: tour arrow color (#41761)

* fix: tour arrow color

* test: updte

* fix: duplicate class name

* test: update snapshot

* chore: speedup

* fix: update current step props by onChange

* chore: optimization

* fix: props.current may change

* chore: save

* test: coverage

* chore: optimization

* chore: add useMergedType

* chore: save

* chore: optimization

* chore: optimization

* chore: optimization

* chore: move file

* refactor: useMergedType hook

* chore: save

* chore: optimization

* test: ci

* chore: remove useMemo

* chore: use nullish coalescing operator

* chore: rename setInnerCurrent to updateInnerCurrent
pull/41892/head
iola1999 2 years ago
committed by GitHub
parent
commit
26555fcd12
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap
  2. 2
      components/tour/__tests__/__snapshots__/demo.test.tsx.snap
  3. 149
      components/tour/__tests__/__snapshots__/index.test.tsx.snap
  4. 51
      components/tour/__tests__/index.test.tsx
  5. 60
      components/tour/__tests__/useMergedType.test.ts
  6. 25
      components/tour/index.tsx
  7. 12
      components/tour/panelRender.tsx
  8. 2
      components/tour/style/index.ts
  9. 31
      components/tour/useMergedType.ts

6
components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap

@ -548,7 +548,7 @@ Array [
</div>
</div>,
<div
class="ant-tour ant-tour-placement-bottom"
class="ant-tour ant-tour-primary ant-tour-placement-bottom"
style="left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
@ -556,7 +556,7 @@ Array [
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tour-primary ant-tour-content"
class="ant-tour-content"
>
<div
class="ant-tour-inner"
@ -954,7 +954,7 @@ exports[`renders components/tour/demo/render-panel.tsx extend context correctly
role="tooltip"
>
<div
class="ant-tour-primary ant-tour-content"
class="ant-tour-content"
>
<div
class="ant-tour-inner"

2
components/tour/__tests__/__snapshots__/demo.test.tsx.snap

@ -538,7 +538,7 @@ exports[`renders components/tour/demo/render-panel.tsx correctly 1`] = `
role="tooltip"
>
<div
class="ant-tour-primary ant-tour-content"
class="ant-tour-content"
>
<div
class="ant-tour-inner"

149
components/tour/__tests__/__snapshots__/index.test.tsx.snap

@ -10,7 +10,7 @@ exports[`Tour Primary 1`] = `
Cover
</button>
<div
class="ant-tour ant-tour-placement-bottom"
class="ant-tour ant-tour-primary ant-tour-placement-bottom"
style="left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
@ -18,7 +18,7 @@ exports[`Tour Primary 1`] = `
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tour-primary ant-tour-content"
class="ant-tour-content"
>
<div
class="ant-tour-inner"
@ -79,7 +79,7 @@ exports[`Tour Primary 1`] = `
</div>
<div>
<div
class="ant-tour-mask"
class="ant-tour-mask ant-tour-primary"
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 1001; pointer-events: none;"
>
<svg
@ -152,7 +152,7 @@ exports[`Tour Primary 1`] = `
</div>
<div>
<div
class="ant-tour-target-placeholder"
class="ant-tour-primary ant-tour-target-placeholder"
style="left: -6px; top: -6px; width: 12px; height: 12px; position: fixed; pointer-events: none;"
/>
</div>
@ -203,6 +203,139 @@ exports[`Tour button props onClick 1`] = `
</body>
`;
exports[`Tour controlled current 1`] = `
<body>
<div>
<div>
<button
type="button"
>
SetCurrent
</button>
</div>
<div
class="ant-tour ant-tour-primary ant-tour-placement-bottom"
style="left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
class="ant-tour-content"
>
<div
class="ant-tour-inner"
>
<span
aria-label="close"
class="anticon anticon-close ant-tour-close"
role="img"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 00203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</span>
<div
class="ant-tour-header"
>
<div
class="ant-tour-title"
>
Primary title
</div>
</div>
<div
class="ant-tour-description"
>
Primary description.
</div>
<div
class="ant-tour-footer"
>
<div
class="ant-tour-indicators"
>
<span
class="ant-tour-indicator"
/>
<span
class="ant-tour-indicator-active ant-tour-indicator"
/>
</div>
<div
class="ant-tour-buttons"
>
<button
class="ant-btn ant-btn-default ant-btn-sm ant-btn-background-ghost ant-tour-prev-btn"
type="button"
>
<span>
Previous
</span>
</button>
<button
class="ant-btn ant-btn-default ant-btn-sm ant-tour-next-btn"
type="button"
>
<span>
Finish
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div
class="ant-tour-mask ant-tour-primary"
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 1001; pointer-events: none;"
>
<svg
style="width: 100%; height: 100%;"
>
<defs>
<mask
id="ant-tour-mask-test-id"
>
<rect
fill="white"
height="100vh"
width="100vw"
x="0"
y="0"
/>
</mask>
</defs>
<rect
fill="rgba(0,0,0,0.5)"
height="100%"
mask="url(#ant-tour-mask-test-id)"
width="100%"
x="0"
y="0"
/>
</svg>
</div>
</div>
<div>
<div
class="ant-tour-primary ant-tour-target-placeholder"
style="left: 50%; top: 50%; width: 1px; height: 1px; position: fixed; pointer-events: none;"
/>
</div>
</body>
`;
exports[`Tour custom step pre btn & next btn className & style 1`] = `
<div
class="ant-tour ant-tour-placement-bottom"
@ -451,7 +584,7 @@ exports[`Tour step support Primary 1`] = `
Cover
</button>
<div
class="ant-tour ant-tour-placement-bottom"
class="ant-tour ant-tour-primary ant-tour-placement-bottom"
style="left: -1000vw; top: -1000vh; box-sizing: border-box; z-index: 1001;"
>
<div
@ -459,7 +592,7 @@ exports[`Tour step support Primary 1`] = `
style="position: absolute; top: 0px; left: 0px;"
/>
<div
class="ant-tour-primary ant-tour-content"
class="ant-tour-content"
>
<div
class="ant-tour-inner"
@ -538,7 +671,7 @@ exports[`Tour step support Primary 1`] = `
</div>
<div>
<div
class="ant-tour-mask"
class="ant-tour-mask ant-tour-primary"
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 1001; pointer-events: none;"
>
<svg
@ -611,7 +744,7 @@ exports[`Tour step support Primary 1`] = `
</div>
<div>
<div
class="ant-tour-target-placeholder"
class="ant-tour-primary ant-tour-target-placeholder"
style="left: -6px; top: -6px; width: 12px; height: 12px; position: fixed; pointer-events: none;"
/>
</div>

51
components/tour/__tests__/index.test.tsx

@ -178,7 +178,9 @@ describe('Tour', () => {
};
const { getByText, baseElement } = render(<App />);
expect(getByText('primary description.')).toBeTruthy();
expect(baseElement.querySelector('.ant-tour-content')).toHaveClass('ant-tour-primary');
expect(baseElement.querySelector('.ant-tour-content')?.parentElement).toHaveClass(
'ant-tour-primary',
);
expect(baseElement).toMatchSnapshot();
});
@ -212,10 +214,10 @@ describe('Tour', () => {
};
const { getByText, container, baseElement } = render(<App />);
expect(getByText('cover description.')).toBeTruthy();
expect(container.querySelector('.ant-tour-content.ant-tour-primary')).toBeFalsy();
expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeFalsy();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(getByText('primary description.')).toBeTruthy();
expect(container.querySelector('.ant-tour-content.ant-tour-primary')).toBeTruthy();
expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy();
expect(baseElement).toMatchSnapshot();
});
@ -381,4 +383,47 @@ describe('Tour', () => {
const { container } = render(<App />);
expect(container.querySelector<HTMLSpanElement>('.custom-indicator')).toBeTruthy();
});
it('controlled current', () => {
const App: React.FC = () => {
const [current, setCurrent] = React.useState(0);
return (
<>
<div>
<button
type="button"
onClick={() => {
setCurrent(1);
}}
>
SetCurrent
</button>
</div>
<Tour
open
current={current}
steps={[
{
title: 'Show in Center',
description: 'Here is the content of Tour.',
},
{
title: 'Primary title',
description: 'Primary description.',
type: 'primary',
},
]}
onChange={setCurrent}
/>
</>
);
};
const { getByText, container, baseElement } = render(<App />);
fireEvent.click(screen.getByRole('button', { name: 'SetCurrent' }));
expect(getByText('Primary description.')).toBeTruthy();
expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy();
expect(baseElement).toMatchSnapshot();
});
});

60
components/tour/__tests__/useMergedType.test.ts

@ -0,0 +1,60 @@
import { act, renderHook } from '../../../tests/utils';
import useMergedType from '../useMergedType';
describe('useMergedType', () => {
it('returns the merged type', () => {
const { result } = renderHook(() =>
useMergedType({
defaultType: 'default',
steps: [{ type: 'primary', title: 'Step 1' }],
current: 0,
}),
);
expect(result.current?.currentMergedType).toBe('primary');
});
it('returns the default type', () => {
const { result } = renderHook(() =>
useMergedType({
defaultType: 'default',
steps: [],
current: 0,
}),
);
expect(result.current?.currentMergedType).toBe('default');
});
it('returns the default type when index is invalid', () => {
const { result } = renderHook(() =>
useMergedType({
defaultType: 'default',
steps: [],
current: 0,
}),
);
expect(result.current?.currentMergedType).toBe('default');
});
it('returns the default type when list is null', () => {
const { result } = renderHook(() =>
useMergedType({
defaultType: 'default',
}),
);
expect(result.current?.currentMergedType).toBe('default');
});
it('returns type of new step after onChange from rc-tour', () => {
const { result } = renderHook(() =>
useMergedType({
defaultType: 'default',
steps: [{ title: 'Step 1' }, { type: 'primary', title: 'Step 1' }],
}),
);
act(() => {
result.current?.updateInnerCurrent?.(1);
});
expect(result.current?.currentMergedType).toBe('primary');
});
});

25
components/tour/index.tsx

@ -1,25 +1,27 @@
import RCTour from '@rc-component/tour';
import classNames from 'classnames';
import React, { useContext } from 'react';
import getPlacements from '../_util/placements';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import theme from '../theme';
import getPlacements from '../_util/placements';
import PurePanel from './PurePanel';
import type { TourProps, TourStepProps } from './interface';
import TourPanel from './panelRender';
import PurePanel from './PurePanel';
import useStyle from './style';
import useMergedType from './useMergedType';
const Tour: React.FC<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel } = (
props,
) => {
const {
prefixCls: customizePrefixCls,
steps,
current,
defaultCurrent,
type,
rootClassName,
indicatorsRender,
steps,
...restProps
} = props;
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(ConfigContext);
@ -27,6 +29,13 @@ const Tour: React.FC<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: type
const [wrapSSR, hashId] = useStyle(prefixCls);
const { token } = theme.useToken();
const { currentMergedType, updateInnerCurrent } = useMergedType({
defaultType: type,
steps,
current,
defaultCurrent,
});
const builtinPlacements = getPlacements({
arrowPointAtCenter: true,
autoAdjustOverflow: true,
@ -37,6 +46,7 @@ const Tour: React.FC<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: type
const customClassName = classNames(
{
[`${prefixCls}-primary`]: currentMergedType === 'primary',
[`${prefixCls}-rtl`]: direction === 'rtl',
},
hashId,
@ -52,16 +62,23 @@ const Tour: React.FC<TourProps> & { _InternalPanelDoNotUseOrYouWillBeFired: type
/>
);
const onStepChange = (stepCurrent: number) => {
updateInnerCurrent(stepCurrent);
props.onChange?.(stepCurrent);
};
return wrapSSR(
<RCTour
{...restProps}
rootClassName={customClassName}
prefixCls={prefixCls}
steps={steps}
current={current}
defaultCurrent={defaultCurrent}
animated
renderPanel={mergedRenderPanel}
builtinPlacements={builtinPlacements}
onChange={onStepChange}
steps={steps}
/>,
);
};

12
components/tour/panelRender.tsx

@ -4,8 +4,8 @@ import type { ReactNode } from 'react';
import React from 'react';
import type { ButtonProps } from '../button';
import Button from '../button';
import defaultLocale from '../locale/en_US';
import { useLocale } from '../locale';
import defaultLocale from '../locale/en_US';
import type { TourStepProps } from './interface';
function isValidNode(node: ReactNode): boolean {
@ -36,7 +36,7 @@ const TourPanel: React.FC<TourPanelProps> = ({ stepProps, current, type, indicat
className,
} = stepProps;
const mergedType = typeof stepType !== 'undefined' ? stepType : type;
const mergedType = stepType ?? type;
const isLastStep = current === total - 1;
@ -94,13 +94,7 @@ const TourPanel: React.FC<TourPanelProps> = ({ stepProps, current, type, indicat
const [contextLocale] = useLocale('Tour', defaultLocale.Tour);
return (
<div
className={classNames(
mergedType === 'primary' ? `${prefixCls}-primary` : '',
className,
`${prefixCls}-content`,
)}
>
<div className={classNames(className, `${prefixCls}-content`)}>
<div className={`${prefixCls}-inner`}>
<CloseOutlined className={`${prefixCls}-close`} onClick={onClose} />
{coverNode}

2
components/tour/style/index.ts

@ -159,9 +159,7 @@ const genBaseStyle: GenerateStyle<TourToken> = (token) => {
// `$` for panel, `&$` for pure panel
[`${componentCls}-primary, &${componentCls}-primary`]: {
'--antd-arrow-background-color': colorPrimary,
},
[`${componentCls}-primary`]: {
[`${componentCls}-inner`]: {
color: colorTextLightSolid,
textAlign: 'start',

31
components/tour/useMergedType.ts

@ -0,0 +1,31 @@
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import { useLayoutEffect } from 'react';
import type { TourProps } from './interface';
interface Props {
defaultType?: string;
steps?: TourProps['steps'];
current?: number;
defaultCurrent?: number;
}
/**
* returns the merged type of a step or the default type.
*/
const useMergedType = ({ defaultType, steps = [], current, defaultCurrent }: Props) => {
const [innerCurrent, updateInnerCurrent] = useMergedState<number | undefined>(defaultCurrent, {
value: current,
});
useLayoutEffect(() => {
if (current === undefined) return;
updateInnerCurrent(current);
}, [current]);
const innerType = typeof innerCurrent === 'number' ? steps[innerCurrent]?.type : defaultType;
const currentMergedType = innerType ?? defaultType;
return { currentMergedType, updateInnerCurrent };
};
export default useMergedType;
Loading…
Cancel
Save