mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
[new component] Next tour (#37867)
* feat: init * feat: update * feat: upate * feat: update * feat: update * feat: init * feat: init * feat: init * feat: update * feat: update * feat: update * feat: update rc-tour * feat: init component * feat: init component * chore: update pck * doc: update doc * doc: update reviewer * doc: update reviewer * doc: update reviewer * feat: update reviewer * feat: update reviewer * feat: update doc * feat: update deme * feat: update demo doc * feat: update demo * feat: update demo * feat: update style * feat: update dom & style * feat: update dome * feat: update dome * docs: update demo * feat: update doc * feat: update dome * feat: add locale * doc: update locale * doc: add test * feat: add test case * feat: add test case * feat: update package * feat: update ts * feat: update ts * feat: update snapshots * feat: update demo * feat: update demo * feat: update demo * feat: edit maxSize * feat: edit maxSize * feat: update lint * feat: update lint * feat: update style reviewer * feat: update style * feat: merge next * feat: add locale * feat: reset bundleSize * feat: change maxSize * feat: update test coverage * feat: update test coverage * feat: add type * chore: simplify en locale * feat: update * feat: update test snap * docs: demo update * chore: adjust style * chore: adjust style * chore: bump rc-tour * Update package.json * feat: update package * feat: update package * feat: update cover * docs: update api * docs: update overview snap * feat: update token * feat: delete repeat ts * feat: remove finishButtonProps * chore: update demo * feat: tour style * test: fix lint * chore: code clean Co-authored-by: lijianan <574980606@qq.com> Co-authored-by: 二货机器人 <smith3816@gmail.com> Co-authored-by: MadCcc <1075746765@qq.com>
This commit is contained in:
parent
8f6f223f2d
commit
b0850139f2
@ -59,6 +59,7 @@ Array [
|
||||
"TimePicker",
|
||||
"Timeline",
|
||||
"Tooltip",
|
||||
"Tour",
|
||||
"Transfer",
|
||||
"Tree",
|
||||
"TreeSelect",
|
||||
|
@ -67,7 +67,7 @@ const genBaseStyle: GenerateStyle<DropdownToken> = token => {
|
||||
// A placeholder out of dropdown visible range to avoid close when user moving
|
||||
'&::before': {
|
||||
position: 'absolute',
|
||||
insetBlock: -dropdownArrowDistance + sizePopupArrow,
|
||||
insetBlock: -dropdownArrowDistance + sizePopupArrow / 2,
|
||||
// insetInlineStart: -7, // FIXME: Seems not work for hidden element
|
||||
zIndex: -9999,
|
||||
opacity: 0.0001,
|
||||
@ -449,7 +449,7 @@ export default genComponentStyleHook(
|
||||
const dropdownToken = mergeToken<DropdownToken>(token, {
|
||||
menuCls: `${componentCls}-menu`,
|
||||
rootPrefixCls,
|
||||
dropdownArrowDistance: sizePopupArrow + marginXXS,
|
||||
dropdownArrowDistance: sizePopupArrow / 2 + marginXXS,
|
||||
dropdownArrowOffset,
|
||||
dropdownPaddingVertical,
|
||||
dropdownEdgeChildPadding: paddingXXS,
|
||||
|
@ -133,6 +133,8 @@ export { default as Timeline } from './timeline';
|
||||
export type { TimelineItemProps, TimelineProps } from './timeline';
|
||||
export { default as Tooltip } from './tooltip';
|
||||
export type { TooltipProps } from './tooltip';
|
||||
export { default as Tour } from './tour';
|
||||
export type { TourProps, TourStepProps } from './tour/interface';
|
||||
export { default as Transfer } from './transfer';
|
||||
export type { TransferProps } from './transfer';
|
||||
export { default as Tree } from './tree';
|
||||
|
@ -4,6 +4,7 @@ import warning from '../_util/warning';
|
||||
import type { PickerLocale as DatePickerLocale } from '../date-picker/generatePicker';
|
||||
import type { TransferLocale as TransferLocaleForEmpty } from '../empty';
|
||||
import type { ModalLocale } from '../modal/locale';
|
||||
import type { TourLocale } from '../tour/interface';
|
||||
import { changeConfirmLocale } from '../modal/locale';
|
||||
import type { PaginationLocale } from '../pagination/Pagination';
|
||||
import type { PopconfirmLocale } from '../popconfirm/PurePanel';
|
||||
@ -23,6 +24,7 @@ export interface Locale {
|
||||
Calendar?: Record<string, any>;
|
||||
Table?: TableLocale;
|
||||
Modal?: ModalLocale;
|
||||
Tour?: TourLocale;
|
||||
Popconfirm?: PopconfirmLocale;
|
||||
Transfer?: TransferLocale;
|
||||
Select?: Record<string, any>;
|
||||
|
@ -35,6 +35,11 @@ const localeValues: Locale = {
|
||||
triggerAsc: 'Click to sort ascending',
|
||||
cancelSort: 'Click to cancel sorting',
|
||||
},
|
||||
Tour: {
|
||||
Next: 'Next',
|
||||
Previous: 'Previous',
|
||||
Finish: 'Finish',
|
||||
},
|
||||
Modal: {
|
||||
okText: 'OK',
|
||||
cancelText: 'Cancel',
|
||||
|
@ -40,6 +40,11 @@ const localeValues: Locale = {
|
||||
cancelText: '取消',
|
||||
justOkText: '知道了',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '结束导览',
|
||||
},
|
||||
Popconfirm: {
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
|
@ -37,6 +37,11 @@ const localeValues: Locale = {
|
||||
cancelText: '取消',
|
||||
justOkText: '知道了',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '結束導覽',
|
||||
},
|
||||
Popconfirm: {
|
||||
okText: '確定',
|
||||
cancelText: '取消',
|
||||
|
@ -37,6 +37,11 @@ const localeValues: Locale = {
|
||||
cancelText: '取消',
|
||||
justOkText: '知道了',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '結束導覽',
|
||||
},
|
||||
Popconfirm: {
|
||||
okText: '確定',
|
||||
cancelText: '取消',
|
||||
|
@ -56,7 +56,7 @@ export default function getArrowStyle<Token extends TokenWithCommonCls<AliasToke
|
||||
borderRadiusOuter,
|
||||
limitVerticalRadius,
|
||||
});
|
||||
const dropdownArrowDistance = sizePopupArrow + marginXXS;
|
||||
const dropdownArrowDistance = sizePopupArrow / 2 + marginXXS;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
|
@ -46,6 +46,7 @@ import type { ComponentToken as TooltipComponentToken } from '../tooltip/style';
|
||||
import type { ComponentToken as TransferComponentToken } from '../transfer/style';
|
||||
import type { ComponentToken as TypographyComponentToken } from '../typography/style';
|
||||
import type { ComponentToken as UploadComponentToken } from '../upload/style';
|
||||
import type { ComponentToken as TourComponentToken } from '../tour/style';
|
||||
|
||||
export const PresetColors = [
|
||||
'blue',
|
||||
@ -133,6 +134,7 @@ export interface ComponentTokenMap {
|
||||
Table?: TableComponentToken;
|
||||
Space?: SpaceComponentToken;
|
||||
Progress?: ProgressComponentToken;
|
||||
Tour?: TourComponentToken;
|
||||
}
|
||||
|
||||
export type OverrideToken = {
|
||||
|
162
components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap
Normal file
162
components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap
Normal file
@ -0,0 +1,162 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/tour/demo/basic.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal"
|
||||
role="separator"
|
||||
/>,
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Upload
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Save
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="ellipsis"
|
||||
class="anticon anticon-ellipsis"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="ellipsis"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/non-modal.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin non-modal Tour
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal"
|
||||
role="separator"
|
||||
/>,
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Upload
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Save
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="ellipsis"
|
||||
class="anticon anticon-ellipsis"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="ellipsis"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/placement.md extend context correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>
|
||||
`;
|
162
components/tour/__tests__/__snapshots__/demo.test.ts.snap
Normal file
162
components/tour/__tests__/__snapshots__/demo.test.ts.snap
Normal file
@ -0,0 +1,162 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/tour/demo/basic.md correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal"
|
||||
role="separator"
|
||||
/>,
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Upload
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Save
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="ellipsis"
|
||||
class="anticon anticon-ellipsis"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="ellipsis"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/non-modal.md correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin non-modal Tour
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal"
|
||||
role="separator"
|
||||
/>,
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Upload
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Save
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="ellipsis"
|
||||
class="anticon anticon-ellipsis"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="ellipsis"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/placement.md correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>
|
||||
`;
|
61
components/tour/__tests__/__snapshots__/index.test.tsx.snap
Normal file
61
components/tour/__tests__/__snapshots__/index.test.tsx.snap
Normal file
@ -0,0 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Tour Primary 1`] = `
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Tour basic 1`] = `
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Placement
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Tour rtl render component should be rendered correctly in RTL direction 1`] = `null`;
|
||||
|
||||
exports[`Tour single 1`] = `
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Tour steps is empty 1`] = `
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`Tour steps props stepRender 1`] = `
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
`;
|
3
components/tour/__tests__/demo-extend.test.ts
Normal file
3
components/tour/__tests__/demo-extend.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('tour');
|
3
components/tour/__tests__/demo.test.ts
Normal file
3
components/tour/__tests__/demo.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('tour');
|
5
components/tour/__tests__/image.test.ts
Normal file
5
components/tour/__tests__/image.test.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Tooltip tour', () => {
|
||||
imageDemoTest('tour');
|
||||
});
|
257
components/tour/__tests__/index.test.tsx
Normal file
257
components/tour/__tests__/index.test.tsx
Normal file
@ -0,0 +1,257 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import Tour from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render, screen } from '../../../tests/utils';
|
||||
|
||||
describe('Tour', () => {
|
||||
mountTest(Tour);
|
||||
rtlTest(Tour);
|
||||
|
||||
it('single', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>();
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
|
||||
<Tour
|
||||
steps={[
|
||||
{
|
||||
title: 'cover title',
|
||||
description: 'cover description.',
|
||||
target: () => coverBtnRef.current,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, container } = render(<App />);
|
||||
expect(getByText('cover title')).toBeTruthy();
|
||||
expect(getByText('cover description.')).toBeTruthy();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('steps is empty', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>();
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
<Tour steps={[]} />
|
||||
<Tour />
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { container } = render(<App />);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('steps props stepRender', () => {
|
||||
const onClickMock = jest.fn();
|
||||
const stepRenderMock = jest.fn();
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>();
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
<Tour
|
||||
type="default"
|
||||
stepRender={stepRenderMock}
|
||||
steps={[
|
||||
{
|
||||
title: 'With Cover',
|
||||
nextButtonProps: {
|
||||
onClick: onClickMock,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With Cover',
|
||||
nextButtonProps: {
|
||||
onClick: onClickMock,
|
||||
},
|
||||
prevButtonProps: {
|
||||
onClick: onClickMock,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With Cover',
|
||||
prevButtonProps: {
|
||||
onClick: onClickMock,
|
||||
},
|
||||
nextButtonProps: {
|
||||
onClick: onClickMock,
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { container } = render(<App />);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
|
||||
expect(onClickMock).toHaveBeenCalledTimes(5);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('button props onClick', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>();
|
||||
const [btnName, steBtnName] = React.useState<string>('defaultBtn');
|
||||
return (
|
||||
<>
|
||||
<span id="btnName">{btnName}</span>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
target
|
||||
</button>
|
||||
|
||||
<Tour
|
||||
steps={[
|
||||
{
|
||||
title: '',
|
||||
description: '',
|
||||
target: () => coverBtnRef.current,
|
||||
nextButtonProps: {
|
||||
onClick: () => steBtnName('nextButton'),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
target: () => coverBtnRef.current,
|
||||
prevButtonProps: {
|
||||
onClick: () => steBtnName('prevButton'),
|
||||
},
|
||||
nextButtonProps: {
|
||||
onClick: () => steBtnName('finishButton'),
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { container } = render(<App />);
|
||||
expect(container.querySelector('#btnName')).toHaveTextContent('defaultBtn');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(container.querySelector('#btnName')).toHaveTextContent('nextButton');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
|
||||
expect(container.querySelector('#btnName')).toHaveTextContent('prevButton');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
|
||||
expect(container.querySelector('#btnName')).toHaveTextContent('finishButton');
|
||||
});
|
||||
|
||||
it('Primary', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>();
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
|
||||
<Tour
|
||||
type="primary"
|
||||
steps={[
|
||||
{
|
||||
title: 'primary title',
|
||||
description: 'primary description.',
|
||||
target: () => coverBtnRef.current,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, container } = render(<App />);
|
||||
expect(getByText('primary description.')).toBeTruthy();
|
||||
expect(document.querySelector('.ant-tour')).toHaveClass('ant-tour-primary');
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('basic', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<any>(null);
|
||||
const placementBtnRef = useRef<any>(null);
|
||||
|
||||
const [show, setShow] = React.useState<boolean | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (show === false) {
|
||||
setShow(true);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
<button disabled ref={placementBtnRef} type="button">
|
||||
Placement
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{show && (
|
||||
<Tour
|
||||
steps={[
|
||||
{
|
||||
title: 'Show in Center',
|
||||
description: 'Here is the content of Tour.',
|
||||
target: null,
|
||||
},
|
||||
{
|
||||
title: 'With Cover',
|
||||
description: 'Here is the content of Tour.',
|
||||
target: () => coverBtnRef.current,
|
||||
cover: (
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Adjust Placement',
|
||||
description: 'Here is the content of Tour which show on the right.',
|
||||
placement: 'right',
|
||||
target: () => placementBtnRef.current,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, container } = render(<App />);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Show' }));
|
||||
expect(getByText('Show in Center')).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(getByText('Here is the content of Tour.')).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(getByText('Adjust Placement')).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
|
||||
expect(document.querySelector('.rc-tour')).toBeFalsy();
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
75
components/tour/demo/basic.md
Normal file
75
components/tour/demo/basic.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基本
|
||||
en-US: Basic
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
最简单的用法。
|
||||
|
||||
## en-US
|
||||
|
||||
The most basic usage.
|
||||
|
||||
```tsx
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Button, Divider, Space, Tour } from 'antd';
|
||||
import type { TourProps } from 'antd';
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const ref1 = useRef(null);
|
||||
const ref2 = useRef(null);
|
||||
const ref3 = useRef(null);
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
cover: (
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
),
|
||||
target: () => ref1.current,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.current,
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.current,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="primary" onClick={() => setOpen(true)}>
|
||||
Begin Tour
|
||||
</Button>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Space>
|
||||
<Button ref={ref1}> Upload</Button>
|
||||
<Button ref={ref2} type="primary">
|
||||
Save
|
||||
</Button>
|
||||
<Button ref={ref3} icon={<EllipsisOutlined />} />
|
||||
</Space>
|
||||
|
||||
<Tour open={open} onClose={() => setOpen(false)} steps={steps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
75
components/tour/demo/non-modal.md
Normal file
75
components/tour/demo/non-modal.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
order: 1
|
||||
title:
|
||||
zh-CN: 非模态
|
||||
en-US: Non-modal
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
使用 `mask={false}` 可以将引导变为非模态,同时为了强调引导本身,建议与 `type="primary"` 组合使用。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `mask={false}` to make Tour non-modal. At the meantime it is recommended to use with `type="primary"` to emphasize the guide itself.
|
||||
|
||||
```tsx
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Button, Divider, Space, Tour } from 'antd';
|
||||
import type { TourProps } from 'antd';
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const ref1 = useRef(null);
|
||||
const ref2 = useRef(null);
|
||||
const ref3 = useRef(null);
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
cover: (
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
),
|
||||
target: () => ref1.current,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.current,
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.current,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="primary" onClick={() => setOpen(true)}>
|
||||
Begin non-modal Tour
|
||||
</Button>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Space>
|
||||
<Button ref={ref1}> Upload</Button>
|
||||
<Button ref={ref2} type="primary">
|
||||
Save
|
||||
</Button>
|
||||
<Button ref={ref3} icon={<EllipsisOutlined />} />
|
||||
</Space>
|
||||
|
||||
<Tour open={open} onClose={() => setOpen(false)} mask={false} type="primary" steps={steps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
58
components/tour/demo/placement.md
Normal file
58
components/tour/demo/placement.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 位置
|
||||
en-US: Placement
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
改变引导相对于目标的位置,共有 12 种位置可供选择。当 `target={null}` 时引导将会展示在正中央。
|
||||
|
||||
## en-US
|
||||
|
||||
Change the placement of the guide relative to the target, there are 12 placements available. When `target={null}` the guide will show in the center.
|
||||
|
||||
```tsx
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Button, Tour } from 'antd';
|
||||
import type { TourProps } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Center',
|
||||
description: 'Displayed in the center of screen.',
|
||||
target: null,
|
||||
},
|
||||
{
|
||||
title: 'Right',
|
||||
description: 'On the right of target.',
|
||||
placement: 'right',
|
||||
target: () => ref.current,
|
||||
},
|
||||
{
|
||||
title: 'Top',
|
||||
description: 'On the top of target.',
|
||||
placement: 'top',
|
||||
target: () => ref.current,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="primary" onClick={() => setOpen(true)} ref={ref}>
|
||||
Begin Tour
|
||||
</Button>
|
||||
|
||||
<Tour open={open} onClose={() => setOpen(false)} steps={steps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
41
components/tour/index.en-US.md
Normal file
41
components/tour/index.en-US.md
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: Walk Through
|
||||
type: Data Entry
|
||||
title: Tour
|
||||
cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg
|
||||
---
|
||||
|
||||
A popup component for guiding users through a product. Available since `5.0.0`.
|
||||
|
||||
## When To Use
|
||||
|
||||
Use when you want to guide users through a product.
|
||||
|
||||
## API
|
||||
|
||||
### Tour
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| arrow | Whether to show the arrow, including the configuration whether to point to the center of the elemen | `boolean`\|`{ pointAtCenter: boolean}` | `true` | |
|
||||
| placement | Position of the guide card relative to the target element | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
|
||||
| onClose | Callback function on shutdown | `Function` | - | |
|
||||
| mask | Whether to enable masking | `boolean` | `true` | |
|
||||
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
||||
|
||||
### TourStep
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| target | Get the element the guide card points to. Empty makes it show in center of screen | `() => HTMLElement` `HTMLElement` | - | |
|
||||
| arrow | Whether to show the arrow, including the configuration whether to point to the center of the element | `boolean` `{ pointAtCenter: boolean}` | `true` | |
|
||||
| cover | Displayed pictures or videos | `ReactNode` | - | |
|
||||
| title | title | `ReactNode` | - | |
|
||||
| description | description | `ReactNode` | - | |
|
||||
| placement | Position of the guide card relative to the target element | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
|
||||
| onClose | Callback function on shutdown | `Function` | - | |
|
||||
| mask | Whether to enable masking, the default follows the `mask` property of Tour | `boolean` | `true` | |
|
||||
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
||||
| nextButtonProps | Properties of the Next button | `{ children: ReactNode; onClick: Function }` | - | |
|
||||
| prevButtonProps | Properties of the previous button | `{ children: ReactNode; onClick: Function }` | - | |
|
53
components/tour/index.tsx
Normal file
53
components/tour/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useContext } from 'react';
|
||||
import RCTour from '@rc-component/tour';
|
||||
import classNames from 'classnames';
|
||||
import panelRender from './panelRender';
|
||||
import type { ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import useStyle from './style';
|
||||
import type { TourProps, TourStepProps } from './interface';
|
||||
|
||||
const Tour: React.ForwardRefRenderFunction<HTMLDivElement, TourProps> = props => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
steps,
|
||||
current,
|
||||
type,
|
||||
rootClassName,
|
||||
...restProps
|
||||
} = props;
|
||||
const { getPrefixCls, direction } = useContext<ConfigConsumerProps>(ConfigContext);
|
||||
const prefixCls = getPrefixCls('tour', customizePrefixCls);
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
const customClassName = classNames(
|
||||
{
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
{
|
||||
[`${prefixCls}-primary`]: type === 'primary',
|
||||
},
|
||||
hashId,
|
||||
rootClassName,
|
||||
);
|
||||
|
||||
const mergedRenderPanel = (stepProps: TourStepProps, stepCurrent: number) =>
|
||||
panelRender(stepProps, stepCurrent, type);
|
||||
|
||||
return wrapSSR(
|
||||
<RCTour
|
||||
{...restProps}
|
||||
rootClassName={customClassName}
|
||||
prefixCls={prefixCls}
|
||||
steps={steps}
|
||||
current={current}
|
||||
renderPanel={mergedRenderPanel}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Tour.displayName = 'Tour';
|
||||
}
|
||||
|
||||
export default Tour;
|
42
components/tour/index.zh-CN.md
Normal file
42
components/tour/index.zh-CN.md
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: 漫游式引导
|
||||
type: 数据展示
|
||||
title: Tour
|
||||
cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg
|
||||
---
|
||||
|
||||
用于分步引导用户了解产品功能的气泡组件。自 `5.0.0` 版本开始提供该组件。
|
||||
|
||||
## 何时使用
|
||||
|
||||
常用于引导用户了解产品功能。
|
||||
|
||||
## API
|
||||
|
||||
### Tour
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| arrow | 是否显示箭头,包含是否指向元素中心的配置 | `boolean` \| `{ pointAtCenter: boolean}` | `true` | |
|
||||
| placement | 引导卡片相对于目标元素的位置 | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
|
||||
| onClose | 关闭引导时的回调函数 | `Function` | - | |
|
||||
| onFinish | 引导完成时的回调 | `Function` | - | |
|
||||
| mask | 是否启用蒙层 | `boolean` | `true` | |
|
||||
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
||||
|
||||
### TourStep 引导步骤卡片
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| target | 获取引导卡片指向的元素,为空时居中于屏幕 | `() => HTMLElement` \| `HTMLElement` | - | |
|
||||
| arrow | 是否显示箭头,包含是否指向元素中心的配置 | `boolean` \| `{ pointAtCenter: boolean}` | `true` | |
|
||||
| cover | 展示的图片或者视频 | `ReactNode` | - | |
|
||||
| title | 标题 | `ReactNode` | - | |
|
||||
| description | 主要描述部分 | `ReactNode` | - | |
|
||||
| placement | 引导卡片相对于目标元素的位置 | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` `bottom` | | |
|
||||
| onClose | 关闭引导时的回调函数 | `Function` | - | |
|
||||
| mask | 是否启用蒙层,默认跟随 Tour 的 `mask` 属性 | `boolean` | `true` | |
|
||||
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
||||
| nextButtonProps | 下一步按钮的属性 | `{ children: ReactNode; onClick: Function }` | - | |
|
||||
| prevButtonProps | 上一步按钮的属性 | `{ children: ReactNode; onClick: Function }` | - | |
|
28
components/tour/interface.ts
Normal file
28
components/tour/interface.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type {
|
||||
TourProps as RCTourProps,
|
||||
TourStepProps as RCTourStepProps,
|
||||
} from '@rc-component/tour';
|
||||
|
||||
export type TourProps = Omit<RCTourProps, 'renderPanel'> & {
|
||||
steps?: TourStepProps[];
|
||||
className?: string;
|
||||
prefixCls?: string;
|
||||
current?: number;
|
||||
stepRender?: (current: number, total: number) => ReactNode;
|
||||
type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色
|
||||
};
|
||||
|
||||
export interface TourStepProps extends RCTourStepProps {
|
||||
cover?: ReactNode; // 展示的图片或者视频
|
||||
nextButtonProps?: { children?: ReactNode; onClick?: () => void };
|
||||
prevButtonProps?: { children?: ReactNode; onClick?: () => void };
|
||||
stepRender?: (current: number, total: number) => ReactNode;
|
||||
type?: 'default' | 'primary'; // default 类型,影响底色与文字颜色
|
||||
}
|
||||
|
||||
export interface TourLocale {
|
||||
Next: string;
|
||||
Previous: string;
|
||||
Finish: string;
|
||||
}
|
128
components/tour/panelRender.tsx
Normal file
128
components/tour/panelRender.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import type { TourStepProps } from './interface';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import Button from '../button';
|
||||
import type { ButtonProps } from '../button';
|
||||
import defaultLocale from '../locale/en_US';
|
||||
|
||||
const panelRender: (
|
||||
step: TourStepProps,
|
||||
current: number,
|
||||
type: TourStepProps['type'],
|
||||
) => ReactNode = (props: TourStepProps, current: number, type) => {
|
||||
const {
|
||||
prefixCls,
|
||||
total = 1,
|
||||
title,
|
||||
onClose,
|
||||
onPrev,
|
||||
onNext,
|
||||
onFinish,
|
||||
cover,
|
||||
description,
|
||||
nextButtonProps,
|
||||
prevButtonProps,
|
||||
stepRender,
|
||||
} = props;
|
||||
|
||||
const isLastStep = current === total - 1;
|
||||
|
||||
const prevBtnClick = () => {
|
||||
onPrev?.();
|
||||
if (typeof prevButtonProps?.onClick === 'function') {
|
||||
prevButtonProps?.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
const nextBtnClick = () => {
|
||||
onNext?.();
|
||||
if (isLastStep) {
|
||||
onFinish?.();
|
||||
}
|
||||
if (typeof nextButtonProps?.onClick === 'function') {
|
||||
nextButtonProps?.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
let headerNode: ReactNode;
|
||||
if (title) {
|
||||
headerNode = (
|
||||
<div className={`${prefixCls}-header`}>
|
||||
<div className={`${prefixCls}-title`}>{title}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let descriptionNode: ReactNode;
|
||||
if (description) {
|
||||
descriptionNode = <div className={`${prefixCls}-description`}>{description}</div>;
|
||||
}
|
||||
|
||||
let coverNode: ReactNode;
|
||||
if (cover) {
|
||||
coverNode = <div className={`${prefixCls}-cover`}>{cover}</div>;
|
||||
}
|
||||
|
||||
const mergedSlickNode =
|
||||
(typeof stepRender === 'function' && stepRender(current, total)) ||
|
||||
[...Array.from({ length: total }).keys()].map((stepItem, index) => (
|
||||
<span
|
||||
key={stepItem}
|
||||
className={classNames(
|
||||
index === current && `${prefixCls}-slider-active`,
|
||||
`${prefixCls}-slider`,
|
||||
)}
|
||||
/>
|
||||
));
|
||||
const slickNode: ReactNode = total > 1 ? mergedSlickNode : null;
|
||||
|
||||
const mainBtnType = type === 'primary' ? 'default' : 'primary';
|
||||
const secondaryBtnProps: ButtonProps = {
|
||||
type: 'default',
|
||||
ghost: type === 'primary',
|
||||
};
|
||||
|
||||
return (
|
||||
<LocaleReceiver componentName="Tour" defaultLocale={defaultLocale.Tour}>
|
||||
{contextLocale => (
|
||||
<>
|
||||
<CloseOutlined className={`${prefixCls}-close`} onClick={onClose} />
|
||||
{coverNode}
|
||||
{headerNode}
|
||||
{descriptionNode}
|
||||
<div className={`${prefixCls}-footer`}>
|
||||
<div className={`${prefixCls}-sliders`}>{slickNode}</div>
|
||||
<div className={`${prefixCls}-buttons`}>
|
||||
{current !== 0 ? (
|
||||
<Button
|
||||
{...secondaryBtnProps}
|
||||
{...prevButtonProps}
|
||||
onClick={prevBtnClick}
|
||||
size="small"
|
||||
className={`${prefixCls}-prev-btn`}
|
||||
>
|
||||
{prevButtonProps?.children ?? contextLocale.Previous}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type={mainBtnType}
|
||||
{...nextButtonProps}
|
||||
onClick={nextBtnClick}
|
||||
size="small"
|
||||
className={`${prefixCls}-next-btn`}
|
||||
>
|
||||
{nextButtonProps?.children ??
|
||||
(isLastStep ? contextLocale.Finish : contextLocale.Next)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</LocaleReceiver>
|
||||
);
|
||||
};
|
||||
|
||||
export default panelRender;
|
235
components/tour/style/index.tsx
Normal file
235
components/tour/style/index.tsx
Normal file
@ -0,0 +1,235 @@
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import type { FullToken, GenerateStyle } from '../../theme';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme';
|
||||
import { resetComponent } from '../../style';
|
||||
import getArrowStyle, { MAX_VERTICAL_CONTENT_RADIUS } from '../../style/placementArrow';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface TourToken extends FullToken<'Tour'> {
|
||||
tourZIndexPopup: number;
|
||||
sliderWidth: number;
|
||||
sliderHeight: number;
|
||||
tourBorderRadius: number;
|
||||
tourCloseSize: number;
|
||||
}
|
||||
|
||||
// =============================== Base ===============================
|
||||
const genBaseStyle: GenerateStyle<TourToken> = token => {
|
||||
const {
|
||||
componentCls,
|
||||
lineHeight,
|
||||
padding,
|
||||
paddingXS,
|
||||
borderRadius,
|
||||
borderRadiusXS,
|
||||
colorPrimary,
|
||||
colorText,
|
||||
colorFill,
|
||||
sliderHeight,
|
||||
sliderWidth,
|
||||
boxShadow,
|
||||
tourZIndexPopup,
|
||||
fontSize,
|
||||
colorBgContainer,
|
||||
fontWeightStrong,
|
||||
marginXS,
|
||||
colorTextLightSolid,
|
||||
tourBorderRadius,
|
||||
colorWhite,
|
||||
colorBgTextHover,
|
||||
tourCloseSize,
|
||||
} = token;
|
||||
|
||||
return [
|
||||
{
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
|
||||
color: colorText,
|
||||
position: 'absolute',
|
||||
zIndex: tourZIndexPopup,
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
fontSize,
|
||||
lineHeight,
|
||||
width: 520,
|
||||
'--antd-arrow-background-color': colorBgContainer,
|
||||
|
||||
[`&${componentCls}-hidden`]: {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
// ============================= panel content ===========================
|
||||
[`${componentCls}-content`]: {
|
||||
position: 'relative',
|
||||
},
|
||||
[`${componentCls}-inner`]: {
|
||||
textAlign: 'start',
|
||||
textDecoration: 'none',
|
||||
borderRadius: tourBorderRadius,
|
||||
boxShadow,
|
||||
position: 'relative',
|
||||
backgroundColor: colorBgContainer,
|
||||
border: 'none',
|
||||
backgroundClip: 'padding-box',
|
||||
|
||||
[`${componentCls}-close`]: {
|
||||
position: 'absolute',
|
||||
top: padding,
|
||||
insetInlineEnd: padding,
|
||||
color: token.colorIcon,
|
||||
outline: 'none',
|
||||
width: tourCloseSize,
|
||||
height: tourCloseSize,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
transition: `background-color ${token.motionDurationFast}, color ${token.motionDurationFast}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
'&:hover': {
|
||||
color: token.colorIconHover,
|
||||
backgroundColor: token.wireframe ? 'transparent' : token.colorFillContent,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-cover`]: {
|
||||
textAlign: 'center',
|
||||
padding: `${padding + tourCloseSize + paddingXS}px ${padding}px 0`,
|
||||
img: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
[`${componentCls}-header`]: {
|
||||
padding: `${padding}px ${padding}px ${paddingXS}px`,
|
||||
|
||||
[`${componentCls}-title`]: {
|
||||
lineHeight,
|
||||
fontSize,
|
||||
fontWeight: fontWeightStrong,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-description`]: {
|
||||
padding: `0 ${padding}px`,
|
||||
lineHeight,
|
||||
wordWrap: 'break-word',
|
||||
},
|
||||
|
||||
[`${componentCls}-footer`]: {
|
||||
padding: `${paddingXS}px ${padding}px ${padding}px`,
|
||||
textAlign: 'end',
|
||||
borderRadius: `0 0 ${borderRadiusXS}px ${borderRadiusXS}px`,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
|
||||
[`${componentCls}-sliders`]: {
|
||||
display: 'inline-block',
|
||||
|
||||
[`${componentCls}-slider`]: {
|
||||
width: `${sliderWidth}px`,
|
||||
height: `${sliderHeight}px`,
|
||||
display: 'inline-block',
|
||||
borderRadius: '50%',
|
||||
background: colorFill,
|
||||
marginInlineEnd: sliderHeight,
|
||||
|
||||
'&-active': {
|
||||
background: colorPrimary,
|
||||
},
|
||||
},
|
||||
},
|
||||
[`${componentCls}-buttons button`]: {
|
||||
marginInlineStart: marginXS,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ============================= primary type ===========================
|
||||
[`&${componentCls}-primary`]: {
|
||||
'--antd-arrow-background-color': colorPrimary,
|
||||
[`${componentCls}-inner`]: {
|
||||
color: colorTextLightSolid,
|
||||
textAlign: 'start',
|
||||
textDecoration: 'none',
|
||||
backgroundColor: colorPrimary,
|
||||
borderRadius,
|
||||
boxShadow,
|
||||
|
||||
[`${componentCls}-close`]: {
|
||||
color: colorTextLightSolid,
|
||||
},
|
||||
|
||||
[`${componentCls}-sliders`]: {
|
||||
[`${componentCls}-slider`]: {
|
||||
background: new TinyColor(colorTextLightSolid).setAlpha(0.15).toRgbString(),
|
||||
|
||||
'&-active': {
|
||||
background: colorTextLightSolid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-prev-btn`]: {
|
||||
color: colorTextLightSolid,
|
||||
borderColor: new TinyColor(colorTextLightSolid).setAlpha(0.15).toRgbString(),
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: new TinyColor(colorTextLightSolid).setAlpha(0.15).toRgbString(),
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-next-btn`]: {
|
||||
color: colorPrimary,
|
||||
borderColor: 'transparent',
|
||||
background: colorWhite,
|
||||
|
||||
'&:hover': {
|
||||
background: new TinyColor(colorBgTextHover).onBackground(colorWhite).toRgbString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Limit left and right placement radius
|
||||
[[
|
||||
`&-placement-left`,
|
||||
`&-placement-leftTop`,
|
||||
`&-placement-leftBottom`,
|
||||
`&-placement-right`,
|
||||
`&-placement-rightTop`,
|
||||
`&-placement-rightBottom`,
|
||||
].join(',')]: {
|
||||
[`${componentCls}-inner`]: {
|
||||
borderRadius:
|
||||
tourBorderRadius > MAX_VERTICAL_CONTENT_RADIUS
|
||||
? MAX_VERTICAL_CONTENT_RADIUS
|
||||
: tourBorderRadius,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Arrow Style
|
||||
getArrowStyle<TourToken>(token, {
|
||||
colorBg: 'var(--antd-arrow-background-color)',
|
||||
showArrowCls: '',
|
||||
contentRadius: tourBorderRadius,
|
||||
limitVerticalRadius: true,
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Tour', token => {
|
||||
const { borderRadiusLG, fontSize, lineHeight } = token;
|
||||
const TourToken = mergeToken<TourToken>(token, {
|
||||
tourZIndexPopup: token.zIndexPopupBase + 70,
|
||||
sliderWidth: 6,
|
||||
sliderHeight: 6,
|
||||
tourBorderRadius: borderRadiusLG,
|
||||
tourCloseSize: fontSize * lineHeight,
|
||||
});
|
||||
return [genBaseStyle(TourToken)];
|
||||
});
|
@ -114,6 +114,7 @@
|
||||
"@ant-design/react-slick": "~0.29.1",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@ctrl/tinycolor": "^3.4.0",
|
||||
"@rc-component/tour": "~1.0.0-9",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"dayjs": "^1.11.1",
|
||||
@ -314,7 +315,7 @@
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./dist/antd.min.js",
|
||||
"maxSize": "375 kB"
|
||||
"maxSize": "377 kB"
|
||||
}
|
||||
],
|
||||
"tnpm": {
|
||||
|
@ -59,6 +59,7 @@ Array [
|
||||
"TimePicker",
|
||||
"Timeline",
|
||||
"Tooltip",
|
||||
"Tour",
|
||||
"Transfer",
|
||||
"Tree",
|
||||
"TreeSelect",
|
||||
|
Loading…
Reference in New Issue
Block a user