mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-12-02 03:58:05 +08:00
feat: add tour (#6332)
* feat v4 add tour * fix type error * sync tour from antd5.4.6 & fix type error * fix error
This commit is contained in:
parent
db4148af5a
commit
698c0ff3b4
30
components/_util/hooks/useId.ts
Normal file
30
components/_util/hooks/useId.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ref } from 'vue';
|
||||
import canUseDom from '../../_util/canUseDom';
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
/** Is client side and not jsdom */
|
||||
export const isBrowserClient = process.env.NODE_ENV !== 'test' && canUseDom();
|
||||
|
||||
/** Get unique id for accessibility usage */
|
||||
export function getUUID(): number | string {
|
||||
let retId: string | number;
|
||||
|
||||
// Test never reach
|
||||
/* istanbul ignore if */
|
||||
if (isBrowserClient) {
|
||||
retId = uuid;
|
||||
uuid += 1;
|
||||
} else {
|
||||
retId = 'TEST_OR_SSR';
|
||||
}
|
||||
|
||||
return retId;
|
||||
}
|
||||
|
||||
export default function useId(id = ref('')) {
|
||||
// Inner id for accessibility usage. Only work in client side
|
||||
const innerId = `vc_unique_${getUUID()}`;
|
||||
|
||||
return id.value || innerId;
|
||||
}
|
@ -258,3 +258,6 @@ export { default as Segmented } from './segmented';
|
||||
|
||||
export type { QRCodeProps } from './qrcode';
|
||||
export { default as QRCode } from './qrcode';
|
||||
|
||||
export type { TourProps, TourStepProps } from './tour';
|
||||
export { default as Tour } from './tour';
|
||||
|
@ -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',
|
||||
|
@ -10,6 +10,7 @@ import type { PickerLocale as DatePickerLocale } from '../date-picker/generatePi
|
||||
import type { PaginationLocale } from '../pagination/Pagination';
|
||||
import type { TableLocale } from '../table/interface';
|
||||
import type { UploadLocale } from '../upload/interface';
|
||||
import type { TourLocale } from '../tour/interface';
|
||||
|
||||
interface TransferLocaleForEmpty {
|
||||
description: string;
|
||||
@ -43,6 +44,7 @@ export interface Locale {
|
||||
copied?: any;
|
||||
expand?: any;
|
||||
};
|
||||
Tour?: TourLocale;
|
||||
QRCode?: {
|
||||
expired?: string;
|
||||
refresh?: string;
|
||||
|
@ -33,6 +33,11 @@ const localeValues: Locale = {
|
||||
triggerAsc: 'Clique organiza por ascendente',
|
||||
cancelSort: 'Clique para cancelar organização',
|
||||
},
|
||||
Tour: {
|
||||
Next: 'Próximo',
|
||||
Previous: 'Anterior',
|
||||
Finish: 'Finalizar',
|
||||
},
|
||||
Modal: {
|
||||
okText: 'OK',
|
||||
cancelText: 'Cancelar',
|
||||
|
@ -35,6 +35,11 @@ const localeValues: Locale = {
|
||||
triggerAsc: '点击升序',
|
||||
cancelSort: '取消排序',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '结束导览',
|
||||
},
|
||||
Modal: {
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
|
@ -32,6 +32,11 @@ const localeValues: Locale = {
|
||||
triggerAsc: '點擊升序',
|
||||
cancelSort: '取消排序',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '結束導覽',
|
||||
},
|
||||
Modal: {
|
||||
okText: '確定',
|
||||
cancelText: '取消',
|
||||
|
@ -32,6 +32,11 @@ const localeValues: Locale = {
|
||||
triggerAsc: '點擊升序',
|
||||
cancelSort: '取消排序',
|
||||
},
|
||||
Tour: {
|
||||
Next: '下一步',
|
||||
Previous: '上一步',
|
||||
Finish: '結束導覽',
|
||||
},
|
||||
Modal: {
|
||||
okText: '確定',
|
||||
cancelText: '取消',
|
||||
|
@ -94,6 +94,7 @@ export interface AliasToken extends MapToken {
|
||||
|
||||
boxShadow: string;
|
||||
boxShadowSecondary: string;
|
||||
boxShadowTertiary: string;
|
||||
|
||||
linkDecoration: CSSProperties['textDecoration'];
|
||||
linkHoverDecoration: CSSProperties['textDecoration'];
|
||||
|
@ -44,7 +44,7 @@ import type { ComponentToken as TooltipComponentToken } from '../../tooltip/styl
|
||||
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';
|
||||
import type { ComponentToken as TourComponentToken } from '../../tour/style';
|
||||
import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
|
||||
// import type { ComponentToken as AppComponentToken } from '../../app/style';
|
||||
import type { ComponentToken as WaveToken } from '../../_util/wave/style';
|
||||
@ -110,7 +110,7 @@ export interface ComponentTokenMap {
|
||||
Table?: TableComponentToken;
|
||||
Space?: SpaceComponentToken;
|
||||
Progress?: ProgressComponentToken;
|
||||
// Tour?: TourComponentToken;
|
||||
Tour?: TourComponentToken;
|
||||
QRCode?: QRCodeComponentToken;
|
||||
// App?: AppComponentToken;
|
||||
|
||||
|
@ -134,6 +134,11 @@ export default function formatToken(derivativeToken: RawMergedToken): AliasToken
|
||||
0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05)
|
||||
`,
|
||||
boxShadowTertiary: `
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0.03),
|
||||
0 1px 6px -1px rgba(0, 0, 0, 0.02),
|
||||
0 2px 4px 0 rgba(0, 0, 0, 0.02)
|
||||
`,
|
||||
|
||||
screenXS,
|
||||
screenXSMin: screenXS,
|
||||
|
478
components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap
Normal file
478
components/tour/__tests__/__snapshots__/demo-extend.test.ts.snap
Normal file
@ -0,0 +1,478 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/tour/demo/basic.tsx 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.tsx 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.tsx extend context correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/render-panel.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
style="display:flex;flex-direction:column;row-gap:16px;background:rgba(50,0,0,0.65);padding:8px"
|
||||
>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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-cover"
|
||||
>
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-header"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-title"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-sm ant-tour-prev-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Previous
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure ant-tour-primary"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-primary ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
</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>
|
||||
`;
|
478
components/tour/__tests__/__snapshots__/demo.test.ts.snap
Normal file
478
components/tour/__tests__/__snapshots__/demo.test.ts.snap
Normal file
@ -0,0 +1,478 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/tour/demo/basic.tsx 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.tsx 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.tsx correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Begin Tour
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/tour/demo/render-panel.tsx correctly 1`] = `
|
||||
<div
|
||||
style="display:flex;flex-direction:column;row-gap:16px;background:rgba(50,0,0,0.65);padding:8px"
|
||||
>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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-cover"
|
||||
>
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-header"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-title"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-sm ant-tour-prev-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Previous
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour ant-tour-pure ant-tour-placement-top ant-tour-pure ant-tour-primary"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-primary ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-inner"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close ant-tour-close"
|
||||
role="img"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Hello World!
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Hello World?!
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
</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>
|
||||
`;
|
706
components/tour/__tests__/__snapshots__/index.test.tsx.snap
Normal file
706
components/tour/__tests__/__snapshots__/index.test.tsx.snap
Normal file
@ -0,0 +1,706 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Tour Primary 1`] = `
|
||||
<body>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour"
|
||||
style="z-index: 1090; opacity: 0;"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-primary ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<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-sliders"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<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>
|
||||
<div
|
||||
class="ant-tour-target-placeholder"
|
||||
style="left: -6px; top: -6px; width: 12px; height: 12px; position: fixed; pointer-events: none;"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour-mask"
|
||||
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 900; pointer-events: none;"
|
||||
>
|
||||
<svg
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<defs>
|
||||
<mask
|
||||
id="ant-tour-mask-test-id"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="100%"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
class="ant-tour-placeholder-animated"
|
||||
fill="black"
|
||||
height="12"
|
||||
rx="2"
|
||||
width="12"
|
||||
x="-6"
|
||||
y="-6"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="-6"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="-6"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="calc(100vh - 6px)"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="6"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="calc(100vw - 6px)"
|
||||
x="6"
|
||||
y="0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Tour basic 1`] = `
|
||||
<body>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Placement
|
||||
</button>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Tour button props onClick 1`] = `
|
||||
<body>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div>
|
||||
<span
|
||||
id="btnName"
|
||||
>
|
||||
finishButton
|
||||
</span>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
target
|
||||
</button>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Tour custom step pre btn & next btn className & style 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour"
|
||||
style="z-index: 1090; opacity: 0;"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Show in Center
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
Here is the content of Tour.
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn customClassName"
|
||||
style="background-color: rgb(69, 69, 255);"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Tour rtl render component should be rendered correctly in RTL direction 1`] = `null`;
|
||||
|
||||
exports[`Tour single 1`] = `
|
||||
<body>
|
||||
<div>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour"
|
||||
style="z-index: 1090; opacity: 0;"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
cover title
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-description"
|
||||
>
|
||||
cover description.
|
||||
</div>
|
||||
<div
|
||||
class="ant-tour-footer"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-sliders"
|
||||
/>
|
||||
<div
|
||||
class="ant-tour-buttons"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-tour-next-btn"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Finish
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour-target-placeholder"
|
||||
style="left: -6px; top: -6px; width: 12px; height: 12px; position: fixed; pointer-events: none;"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour-mask"
|
||||
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 900; pointer-events: none;"
|
||||
>
|
||||
<svg
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<defs>
|
||||
<mask
|
||||
id="ant-tour-mask-test-id"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="100%"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
class="ant-tour-placeholder-animated"
|
||||
fill="black"
|
||||
height="12"
|
||||
rx="2"
|
||||
width="12"
|
||||
x="-6"
|
||||
y="-6"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="-6"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="-6"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="calc(100vh - 6px)"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="6"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="calc(100vw - 6px)"
|
||||
x="6"
|
||||
y="0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Tour step support Primary 1`] = `
|
||||
<body>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour"
|
||||
style="z-index: 1090; opacity: 0;"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-primary ant-tour-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tour-arrow"
|
||||
/>
|
||||
<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-sliders"
|
||||
>
|
||||
<span
|
||||
class="ant-tour-slider"
|
||||
/>
|
||||
<span
|
||||
class="ant-tour-slider-active ant-tour-slider"
|
||||
/>
|
||||
</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>
|
||||
<div
|
||||
class="ant-tour-target-placeholder"
|
||||
style="left: -6px; top: -6px; width: 12px; height: 12px; position: fixed; pointer-events: none;"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-tour-mask"
|
||||
style="position: fixed; left: 0px; right: 0px; top: 0px; bottom: 0px; z-index: 900; pointer-events: none;"
|
||||
>
|
||||
<svg
|
||||
style="width: 100%; height: 100%;"
|
||||
>
|
||||
<defs>
|
||||
<mask
|
||||
id="ant-tour-mask-test-id"
|
||||
>
|
||||
<rect
|
||||
fill="white"
|
||||
height="100%"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
class="ant-tour-placeholder-animated"
|
||||
fill="black"
|
||||
height="12"
|
||||
rx="2"
|
||||
width="12"
|
||||
x="-6"
|
||||
y="-6"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="-6"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="-6"
|
||||
x="0"
|
||||
y="0"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="calc(100vh - 6px)"
|
||||
pointer-events="auto"
|
||||
width="100%"
|
||||
x="0"
|
||||
y="6"
|
||||
/>
|
||||
<rect
|
||||
fill="transparent"
|
||||
height="100%"
|
||||
pointer-events="auto"
|
||||
width="calc(100vw - 6px)"
|
||||
x="6"
|
||||
y="0"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Tour steps is empty 1`] = `
|
||||
<body>
|
||||
<div
|
||||
style="position: absolute; top: 0px; left: 0px; width: 100%;"
|
||||
/>
|
||||
<div>
|
||||
<button
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Cover
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
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');
|
||||
});
|
287
components/tour/__tests__/index.test.tsx
Normal file
287
components/tour/__tests__/index.test.tsx
Normal file
@ -0,0 +1,287 @@
|
||||
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';
|
||||
import panelRender from '../panelRender';
|
||||
|
||||
describe('Tour', () => {
|
||||
mountTest(Tour);
|
||||
rtlTest(Tour);
|
||||
|
||||
it('single', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
<Tour
|
||||
steps={[
|
||||
{
|
||||
title: 'cover title',
|
||||
description: 'cover description.',
|
||||
target: () => coverBtnRef.current!,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, baseElement } = render(<App />);
|
||||
expect(getByText('cover title')).toBeTruthy();
|
||||
expect(getByText('cover description.')).toBeTruthy();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('steps is empty', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
<Tour steps={[]} />
|
||||
<Tour />
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('button props onClick', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
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 { baseElement } = render(<App />);
|
||||
expect(baseElement.querySelector('#btnName')).toHaveTextContent('defaultBtn');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(baseElement.querySelector('#btnName')).toHaveTextContent('nextButton');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
|
||||
expect(baseElement.querySelector('#btnName')).toHaveTextContent('prevButton');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
|
||||
expect(baseElement.querySelector('#btnName')).toHaveTextContent('finishButton');
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Primary', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
|
||||
<Tour
|
||||
type="primary"
|
||||
steps={[
|
||||
{
|
||||
title: 'primary title',
|
||||
description: 'primary description.',
|
||||
target: () => coverBtnRef.current!,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, baseElement } = render(<App />);
|
||||
expect(getByText('primary description.')).toBeTruthy();
|
||||
expect(baseElement.querySelector('.ant-tour-content')).toHaveClass('ant-tour-primary');
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('step support Primary', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<>
|
||||
<button disabled ref={coverBtnRef} type="button">
|
||||
Cover
|
||||
</button>
|
||||
|
||||
<Tour
|
||||
type="default"
|
||||
steps={[
|
||||
{
|
||||
title: 'cover title',
|
||||
description: 'cover description.',
|
||||
target: () => coverBtnRef.current!,
|
||||
},
|
||||
{
|
||||
title: 'primary title',
|
||||
description: 'primary description.',
|
||||
target: () => coverBtnRef.current!,
|
||||
type: 'primary',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const { getByText, container, baseElement } = render(<App />);
|
||||
expect(getByText('cover description.')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-tour-content.ant-tour-primary')).toBeFalsy();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
|
||||
expect(getByText('primary description.')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-tour-content.ant-tour-primary')).toBeTruthy();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('basic', () => {
|
||||
const App: React.FC = () => {
|
||||
const coverBtnRef = useRef<HTMLButtonElement>(null);
|
||||
const placementBtnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [show, setShow] = React.useState<boolean>();
|
||||
|
||||
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, baseElement } = 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(container.querySelector('.ant-tour')).toBeFalsy();
|
||||
expect(baseElement).toMatchSnapshot();
|
||||
});
|
||||
it('panelRender should correct render when total is undefined', () => {
|
||||
expect(() => {
|
||||
panelRender({ total: undefined, title: <div>test</div> }, 0, 'default');
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('custom step pre btn & next btn className & style', () => {
|
||||
const App: React.FC = () => (
|
||||
<Tour
|
||||
steps={[
|
||||
{
|
||||
title: 'Show in Center',
|
||||
description: 'Here is the content of Tour.',
|
||||
nextButtonProps: {
|
||||
className: 'customClassName',
|
||||
style: {
|
||||
backgroundColor: 'rgb(69,69,255)',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'With Cover',
|
||||
description: 'Here is the content of Tour.',
|
||||
cover: (
|
||||
<img
|
||||
alt="tour.png"
|
||||
src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const { container } = render(<App />);
|
||||
// className
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Next' }).className.includes('customClassName'),
|
||||
).toEqual(true);
|
||||
// style
|
||||
expect(screen.getByRole('button', { name: 'Next' }).style.backgroundColor).toEqual(
|
||||
'rgb(69, 69, 255)',
|
||||
);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
70
components/tour/demo/basic.vue
Normal file
70
components/tour/demo/basic.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<docs>
|
||||
---
|
||||
order: 0
|
||||
title:
|
||||
zh-CN: 基本用法
|
||||
en-US: Basic usage
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
最简单的用法。
|
||||
|
||||
## en-US
|
||||
|
||||
The most basic usage.
|
||||
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button type="primary" @click="handleOpen(true)">Begin Tour</a-button>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-space>
|
||||
<a-button ref="ref1">Upload</a-button>
|
||||
<a-button ref="ref2" type="primary">Save</a-button>
|
||||
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
||||
</a-space>
|
||||
|
||||
<a-tour :open="open" :steps="steps" @close="handleOpen(false)" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, createVNode } from 'vue';
|
||||
import { EllipsisOutlined } from '@ant-design/icons-vue';
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
|
||||
const open = ref<boolean>(false);
|
||||
|
||||
const ref1 = ref(null);
|
||||
const ref2 = ref(null);
|
||||
const ref3 = ref(null);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
cover: createVNode('img', {
|
||||
alt: 'tour.png',
|
||||
src: 'https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png',
|
||||
}),
|
||||
target: () => ref1.value && ref1.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.value && ref2.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.value && ref3.value.$el,
|
||||
},
|
||||
];
|
||||
|
||||
const handleOpen = (val: boolean): void => {
|
||||
open.value = val;
|
||||
};
|
||||
</script>
|
35
components/tour/demo/index.vue
Normal file
35
components/tour/demo/index.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<demo-sort>
|
||||
<basic />
|
||||
<non-modal />
|
||||
<placement />
|
||||
<Mask />
|
||||
<indicator />
|
||||
</demo-sort>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Basic from './basic.vue';
|
||||
import NonModal from './non-modal.vue';
|
||||
import Placement from './placement.vue';
|
||||
import Mask from './mask.vue';
|
||||
import Indicator from './indicator.vue';
|
||||
|
||||
import CN from '../index.zh-CN.md';
|
||||
import US from '../index.en-US.md';
|
||||
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
CN,
|
||||
US,
|
||||
components: {
|
||||
Basic,
|
||||
NonModal,
|
||||
Placement,
|
||||
Mask,
|
||||
Indicator,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
70
components/tour/demo/indicator.vue
Normal file
70
components/tour/demo/indicator.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<docs>
|
||||
---
|
||||
order: 4
|
||||
title:
|
||||
zh-CN: 自定义指示器
|
||||
en-US: custom indicator
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
自定义指示器。
|
||||
|
||||
## en-US
|
||||
|
||||
Custom indicator.
|
||||
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button type="primary" @click="handleOpen(true)">Begin Tour</a-button>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-space>
|
||||
<a-button ref="ref1">Upload</a-button>
|
||||
<a-button ref="ref2" type="primary">Save</a-button>
|
||||
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
||||
</a-space>
|
||||
|
||||
<a-tour :open="open" :steps="steps" @close="handleOpen(false)">
|
||||
<template #indicatorsRender="{ current, total }">
|
||||
<span>{{ current + 1 }} / {{ total }}</span>
|
||||
</template>
|
||||
</a-tour>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { EllipsisOutlined } from '@ant-design/icons-vue';
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
|
||||
const open = ref<boolean>(false);
|
||||
|
||||
const ref1 = ref(null);
|
||||
const ref2 = ref(null);
|
||||
const ref3 = ref(null);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
target: () => ref1.value && ref1.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.value && ref2.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.value && ref3.value.$el,
|
||||
},
|
||||
];
|
||||
|
||||
const handleOpen = (val: boolean): void => {
|
||||
open.value = val;
|
||||
};
|
||||
</script>
|
86
components/tour/demo/mask.vue
Normal file
86
components/tour/demo/mask.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<docs>
|
||||
---
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 自定义遮罩样式
|
||||
en-US: custom mask style
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
自定义遮罩样式。
|
||||
|
||||
## en-US
|
||||
|
||||
custom mask style.
|
||||
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button type="primary" @click="handleOpen(true)">Begin Tour</a-button>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-space>
|
||||
<a-button ref="ref1">Upload</a-button>
|
||||
<a-button ref="ref2" type="primary">Save</a-button>
|
||||
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
||||
</a-space>
|
||||
|
||||
<a-tour
|
||||
:open="open"
|
||||
:steps="steps"
|
||||
:mask="{
|
||||
style: {
|
||||
boxShadow: 'inset 0 0 15px #333',
|
||||
},
|
||||
color: 'rgba(80, 255, 255, .4)',
|
||||
}"
|
||||
@close="handleOpen(false)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, createVNode } from 'vue';
|
||||
import { EllipsisOutlined } from '@ant-design/icons-vue';
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
const open = ref<boolean>(false);
|
||||
|
||||
const ref1 = ref(null);
|
||||
const ref2 = ref(null);
|
||||
const ref3 = ref(null);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
cover: createVNode('img', {
|
||||
alt: 'tour.png',
|
||||
src: 'https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png',
|
||||
}),
|
||||
target: () => ref1.value && ref1.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.value && ref2.value.$el,
|
||||
mask: {
|
||||
style: {
|
||||
boxShadow: 'inset 0 0 15px #fff',
|
||||
},
|
||||
color: 'rgba(40, 0, 255, .4)',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.value && ref3.value.$el,
|
||||
mask: false,
|
||||
},
|
||||
];
|
||||
|
||||
const handleOpen = (val: boolean): void => {
|
||||
open.value = val;
|
||||
};
|
||||
</script>
|
69
components/tour/demo/non-modal.vue
Normal file
69
components/tour/demo/non-modal.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<docs>
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button type="primary" @click="handleOpen(true)">Begin Tour</a-button>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-space>
|
||||
<a-button ref="ref1">Upload</a-button>
|
||||
<a-button ref="ref2" type="primary">Save</a-button>
|
||||
<a-button ref="ref3"><EllipsisOutlined /></a-button>
|
||||
</a-space>
|
||||
|
||||
<a-tour :open="open" :mask="false" type="primary" :steps="steps" @close="handleOpen(false)" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, createVNode } from 'vue';
|
||||
import { EllipsisOutlined } from '@ant-design/icons-vue';
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
const open = ref<boolean>(false);
|
||||
|
||||
const ref1 = ref(null);
|
||||
const ref2 = ref(null);
|
||||
const ref3 = ref(null);
|
||||
|
||||
const steps: TourProps['steps'] = [
|
||||
{
|
||||
title: 'Upload File',
|
||||
description: 'Put your files here.',
|
||||
cover: createVNode('img', {
|
||||
alt: 'tour.png',
|
||||
src: 'https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png',
|
||||
}),
|
||||
target: () => ref1.value && ref1.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Save',
|
||||
description: 'Save your changes.',
|
||||
target: () => ref2.value && ref2.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Other Actions',
|
||||
description: 'Click to see other actions.',
|
||||
target: () => ref3.value && ref3.value.$el,
|
||||
},
|
||||
];
|
||||
|
||||
const handleOpen = (val: boolean): void => {
|
||||
open.value = val;
|
||||
};
|
||||
</script>
|
57
components/tour/demo/placement.vue
Normal file
57
components/tour/demo/placement.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<docs>
|
||||
---
|
||||
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.
|
||||
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button ref="btnRef" type="primary" @click="handleOpen(true)">Begin Tour</a-button>
|
||||
|
||||
<a-tour :open="open" :steps="steps" @close="handleOpen(false)" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type { TourProps } from 'ant-design-vue';
|
||||
|
||||
const open = ref<boolean>(false);
|
||||
|
||||
const btnRef = ref(null);
|
||||
|
||||
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: () => btnRef.value && btnRef.value.$el,
|
||||
},
|
||||
{
|
||||
title: 'Top',
|
||||
description: 'On the top of target.',
|
||||
placement: 'top',
|
||||
target: () => btnRef.value && btnRef.value.$el,
|
||||
},
|
||||
];
|
||||
|
||||
const handleOpen = (val: boolean): void => {
|
||||
open.value = val;
|
||||
};
|
||||
</script>
|
59
components/tour/index.en-US.md
Normal file
59
components/tour/index.en-US.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
category: Components
|
||||
type: Data Display
|
||||
title: Tour
|
||||
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*8CC_Tbe3_e4AAAAAAAAAAAAADrJ8AQ/original
|
||||
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6hQpM0XtEAAAAAAAAAAAAADrJ8AQ/original
|
||||
---
|
||||
|
||||
A popup component for guiding users through a product. Available since `4.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 element | `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` | |
|
||||
| mask | Whether to enable masking, change mask style and fill color by pass custom props | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
||||
| open | Open tour | `boolean` | - | |
|
||||
| current | What is the current step | `number` | - | |
|
||||
| scrollIntoViewOptions | support pass custom scrollIntoView options | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||
| indicatorsRender | custom indicator | `v-slot:indicatorsRender="{current, total}"` | - | |
|
||||
| zIndex | Tour's zIndex | `number` | `1001` | |
|
||||
|
||||
### Tour events
|
||||
|
||||
| Events Name | Description | Arguments | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| close | Callback function on shutdown | `Function` | - | |
|
||||
| finish | Callback function on finished | `Function` | - | |
|
||||
| change | Callback when the step changes. Current is the previous step | `(current: number) => void` |
|
||||
|
||||
### 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 | `VueNode` | - | |
|
||||
| title | title | `VueNode` | - | |
|
||||
| description | description | `VueNode` | - | |
|
||||
| placement | Position of the guide card relative to the target element | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
|
||||
| mask | Whether to enable masking, change mask style and fill color by pass custom props, the default follows the `mask` property of Tour | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||
| type | Type, affects the background color and text color | `default` `primary` | `default` | |
|
||||
| nextButtonProps | Properties of the Next button | `{ children: VueNode; onClick: Function }` | - | |
|
||||
| prevButtonProps | Properties of the previous button | `{ children: VueNode; onClick: Function }` | - | |
|
||||
| scrollIntoViewOptions | support pass custom scrollIntoView options, the default follows the `scrollIntoViewOptions` property of Tour | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||
|
||||
### TourStep events
|
||||
|
||||
| Events Name | Description | Arguments | Version |
|
||||
| ----------- | ----------------------------- | ---------- | ------- | --- |
|
||||
| close | Callback function on shutdown | `Function` | - | |
|
82
components/tour/index.tsx
Normal file
82
components/tour/index.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { defineComponent, toRefs } from 'vue';
|
||||
import VCTour from '../vc-tour';
|
||||
import classNames from '../_util/classNames';
|
||||
import TourPanel from './panelRender';
|
||||
import type { TourProps, TourStepProps } from './interface';
|
||||
import { tourProps } from './interface';
|
||||
import useConfigInject from '../config-provider/hooks/useConfigInject';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import { withInstall } from '../_util/type';
|
||||
import useMergedType from './useMergedType';
|
||||
|
||||
// CSSINJS
|
||||
import useStyle from './style';
|
||||
|
||||
export { TourProps, TourStepProps };
|
||||
|
||||
const Tour = defineComponent({
|
||||
name: 'ATour',
|
||||
props: tourProps(),
|
||||
setup(props, { attrs, emit, slots }) {
|
||||
const { current } = toRefs(props);
|
||||
const { prefixCls, direction } = useConfigInject('tour', props);
|
||||
|
||||
// style
|
||||
const [wrapSSR, hashId] = useStyle(prefixCls);
|
||||
|
||||
const { currentMergedType, updateInnerCurrent } = useMergedType({
|
||||
defaultType: props.type,
|
||||
steps: props.steps,
|
||||
current,
|
||||
defaultCurrent: props.defaultCurrent,
|
||||
});
|
||||
|
||||
return () => {
|
||||
const { steps, current, type, rootClassName, ...restProps } = props;
|
||||
|
||||
const customClassName = classNames(
|
||||
{
|
||||
[`${prefixCls.value}-primary`]: currentMergedType.value === 'primary',
|
||||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||
},
|
||||
hashId.value,
|
||||
rootClassName,
|
||||
);
|
||||
|
||||
const mergedRenderPanel = (stepProps: TourStepProps, stepCurrent: number): VueNode => {
|
||||
return (
|
||||
<TourPanel
|
||||
{...stepProps}
|
||||
type={type}
|
||||
current={stepCurrent}
|
||||
v-slots={{
|
||||
indicatorsRender: slots.indicatorsRender,
|
||||
}}
|
||||
></TourPanel>
|
||||
);
|
||||
};
|
||||
|
||||
const onStepChange = (stepCurrent: number) => {
|
||||
updateInnerCurrent(stepCurrent);
|
||||
emit('change', stepCurrent);
|
||||
};
|
||||
|
||||
return wrapSSR(
|
||||
<VCTour
|
||||
{...attrs}
|
||||
{...restProps}
|
||||
rootClassName={customClassName}
|
||||
prefixCls={prefixCls.value}
|
||||
current={current}
|
||||
defaultCurrent={props.defaultCurrent}
|
||||
animated
|
||||
renderPanel={mergedRenderPanel}
|
||||
onChange={onStepChange}
|
||||
steps={steps}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default withInstall(Tour);
|
60
components/tour/index.zh-CN.md
Normal file
60
components/tour/index.zh-CN.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
category: Components
|
||||
type: 数据展示
|
||||
title: Tour
|
||||
subtitle: 漫游式引导
|
||||
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*8CC_Tbe3_e4AAAAAAAAAAAAADrJ8AQ/original
|
||||
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*nF6hQpM0XtEAAAAAAAAAAAAADrJ8AQ/original
|
||||
---
|
||||
|
||||
用于分步引导用户了解产品功能的气泡组件。自 `4.0.0` 版本开始提供该组件。
|
||||
|
||||
## 何时使用
|
||||
|
||||
常用于引导用户了解产品功能。
|
||||
|
||||
## API
|
||||
|
||||
### Tour
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| arrow | 是否显示箭头,包含是否指向元素中心的配置 | `boolean` \| `{ pointAtCenter: boolean}` | `true` | |
|
||||
| placement | 引导卡片相对于目标元素的位置 | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` | `bottom` | |
|
||||
| mask | 是否启用蒙层,也可传入配置改变蒙层样式和填充色 | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
||||
| open | 打开引导 | `boolean` | - | |
|
||||
| current | 当前处于哪一步 | `number` | - | |
|
||||
| scrollIntoViewOptions | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数 | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||
| indicatorsRender | 自定义指示器 | `v-slot:indicatorsRender="{current, total}"` | - | |
|
||||
| zIndex | Tour 的层级 | `number` | `1001` | |
|
||||
|
||||
### Tour events
|
||||
|
||||
| 事件名称 | 说明 | 回调参数 | 版本 |
|
||||
| -------- | ---------------------------------------- | --------------------------- | ---- | --- |
|
||||
| close | 关闭引导时的回调函数 | `Function` | - | |
|
||||
| finish | 引导完成时的回调 | `Function` | - | |
|
||||
| change | 步骤改变时的回调,current 为当前前的步骤 | `(current: number) => void` | - | |
|
||||
|
||||
### TourStep 引导步骤卡片
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| target | 获取引导卡片指向的元素,为空时居中于屏幕 | `() => HTMLElement` \| `HTMLElement` | - | |
|
||||
| arrow | 是否显示箭头,包含是否指向元素中心的配置 | `boolean` \| `{ pointAtCenter: boolean}` | `true` | |
|
||||
| cover | 展示的图片或者视频 | `VueNode` | - | |
|
||||
| title | 标题 | `VueNode` | - | |
|
||||
| description | 主要描述部分 | `VueNode` | - | |
|
||||
| placement | 引导卡片相对于目标元素的位置 | `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` `bottom` | | |
|
||||
| mask | 是否启用蒙层,也可传入配置改变蒙层样式和填充色,默认跟随 Tour 的 `mask` 属性 | `boolean` \| `{ style?: CSSProperties; color?: string; }` | `true` | |
|
||||
| type | 类型,影响底色与文字颜色 | `default` \| `primary` | `default` | |
|
||||
| nextButtonProps | 下一步按钮的属性 | `{ children: VueNode; onClick: Function }` | - | |
|
||||
| prevButtonProps | 上一步按钮的属性 | `{ children: VueNode; onClick: Function }` | - | |
|
||||
| scrollIntoViewOptions | 是否支持当前元素滚动到视窗内,也可传入配置指定滚动视窗的相关参数,默认跟随 Tour 的 `scrollIntoViewOptions` 属性 | `boolean` \| `ScrollIntoViewOptions` | `true` | |
|
||||
|
||||
### TourStep events
|
||||
|
||||
| 事件名称 | 说明 | 回调参数 | 版本 |
|
||||
| -------- | -------------------- | ---------- | ---- | --- |
|
||||
| close | 关闭引导时的回调函数 | `Function` | - | |
|
41
components/tour/interface.ts
Normal file
41
components/tour/interface.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||
import { tourProps as VCTourProps, tourStepProps as VCTourStepProps } from '../vc-tour';
|
||||
import type { VueNode } from '../_util/type';
|
||||
|
||||
export const tourProps = () => ({
|
||||
...VCTourProps(),
|
||||
steps: { type: Array as PropType<TourStepProps[]> },
|
||||
prefixCls: { type: String },
|
||||
current: { type: Number },
|
||||
type: { type: String as PropType<'default' | 'primary'> }, // default 类型,影响底色与文字颜色
|
||||
});
|
||||
|
||||
export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;
|
||||
|
||||
export interface TourBtnProps {
|
||||
children?: () => VueNode;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const tourStepProps = () => ({
|
||||
...VCTourStepProps(),
|
||||
cover: { type: Object as PropType<VueNode> }, // 展示的图片或者视频
|
||||
nextButtonProps: {
|
||||
type: Object as PropType<TourBtnProps>,
|
||||
},
|
||||
prevButtonProps: {
|
||||
type: Object as PropType<TourBtnProps>,
|
||||
},
|
||||
current: { type: Number },
|
||||
type: { type: String as PropType<'default' | 'primary'> }, // default 类型,影响底色与文字颜色
|
||||
});
|
||||
|
||||
export type TourStepProps = Partial<ExtractPropTypes<ReturnType<typeof tourStepProps>>>;
|
||||
|
||||
export interface TourLocale {
|
||||
Next: string;
|
||||
Previous: string;
|
||||
Finish: string;
|
||||
}
|
154
components/tour/panelRender.tsx
Normal file
154
components/tour/panelRender.tsx
Normal file
@ -0,0 +1,154 @@
|
||||
import { computed, defineComponent, toRefs } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import { tourStepProps } from './interface';
|
||||
import type { TourBtnProps } from './interface';
|
||||
|
||||
import LocaleReceiver from '../locale/LocaleReceiver';
|
||||
import Button from '../button';
|
||||
import type { ButtonProps } from '../button';
|
||||
import defaultLocale from '../locale/en_US';
|
||||
|
||||
import type { VueNode } from '../_util/type';
|
||||
|
||||
const panelRender = defineComponent({
|
||||
props: tourStepProps(),
|
||||
setup(props, { attrs, slots }) {
|
||||
const { current, total } = toRefs(props);
|
||||
|
||||
const isLastStep = computed(() => current.value === total.value - 1);
|
||||
|
||||
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
||||
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
||||
|
||||
const prevBtnClick = e => {
|
||||
props.onPrev?.(e);
|
||||
if (typeof prevButtonProps?.onClick === 'function') {
|
||||
prevButtonProps?.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
const nextBtnClick = e => {
|
||||
if (isLastStep.value) {
|
||||
props.onFinish?.(e);
|
||||
} else {
|
||||
props.onNext?.(e);
|
||||
}
|
||||
if (typeof nextButtonProps?.onClick === 'function') {
|
||||
nextButtonProps?.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
const {
|
||||
prefixCls,
|
||||
title,
|
||||
onClose,
|
||||
|
||||
cover,
|
||||
description,
|
||||
type: stepType,
|
||||
arrow,
|
||||
} = props;
|
||||
|
||||
const prevButtonProps = props.prevButtonProps as TourBtnProps;
|
||||
const nextButtonProps = props.nextButtonProps as TourBtnProps;
|
||||
|
||||
let headerNode: VueNode;
|
||||
if (title) {
|
||||
headerNode = (
|
||||
<div class={`${prefixCls}-header`}>
|
||||
<div class={`${prefixCls}-title`}>{title}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let descriptionNode: VueNode;
|
||||
if (description) {
|
||||
descriptionNode = <div class={`${prefixCls}-description`}>{description}</div>;
|
||||
}
|
||||
|
||||
let coverNode: VueNode;
|
||||
if (cover) {
|
||||
coverNode = <div class={`${prefixCls}-cover`}>{cover}</div>;
|
||||
}
|
||||
|
||||
let mergeIndicatorNode: VueNode;
|
||||
|
||||
if (slots.indicatorsRender) {
|
||||
mergeIndicatorNode = slots.indicatorsRender({ current: current.value, total });
|
||||
} else {
|
||||
mergeIndicatorNode = [...Array.from({ length: total.value }).keys()].map(
|
||||
(stepItem, index) => (
|
||||
<span
|
||||
key={stepItem}
|
||||
class={classNames(
|
||||
index === current.value && `${prefixCls}-indicator-active`,
|
||||
`${prefixCls}-indicator`,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const mainBtnType = stepType === 'primary' ? 'default' : 'primary';
|
||||
const secondaryBtnProps: ButtonProps = {
|
||||
type: 'default',
|
||||
ghost: stepType === 'primary',
|
||||
};
|
||||
|
||||
return (
|
||||
<LocaleReceiver componentName="Tour" defaultLocale={defaultLocale.Tour}>
|
||||
{contextLocale => (
|
||||
<div
|
||||
{...attrs}
|
||||
class={classNames(
|
||||
stepType === 'primary' ? `${prefixCls}-primary` : '',
|
||||
attrs.class,
|
||||
`${prefixCls}-content`,
|
||||
)}
|
||||
>
|
||||
{arrow && <div class={`${prefixCls}-arrow`} key="arrow" />}
|
||||
<div class={`${prefixCls}-inner`}>
|
||||
<CloseOutlined class={`${prefixCls}-close`} onClick={onClose} />
|
||||
{coverNode}
|
||||
{headerNode}
|
||||
{descriptionNode}
|
||||
<div class={`${prefixCls}-footer`}>
|
||||
{total.value > 1 && (
|
||||
<div class={`${prefixCls}-indicators`}>{mergeIndicatorNode}</div>
|
||||
)}
|
||||
<div class={`${prefixCls}-buttons`}>
|
||||
{current.value !== 0 ? (
|
||||
<Button
|
||||
{...secondaryBtnProps}
|
||||
{...prevButtonProps}
|
||||
onClick={prevBtnClick}
|
||||
size="small"
|
||||
class={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)}
|
||||
>
|
||||
{prevButtonProps?.children ?? contextLocale.Previous}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type={mainBtnType}
|
||||
{...nextButtonProps}
|
||||
onClick={nextBtnClick}
|
||||
size="small"
|
||||
class={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)}
|
||||
>
|
||||
{nextButtonProps?.children ??
|
||||
(isLastStep.value ? contextLocale.Finish : contextLocale.Next)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</LocaleReceiver>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default panelRender;
|
250
components/tour/style/index.ts
Normal file
250
components/tour/style/index.ts
Normal file
@ -0,0 +1,250 @@
|
||||
import { TinyColor } from '@ctrl/tinycolor';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
|
||||
import { resetComponent } from '../../style';
|
||||
import getArrowStyle, { MAX_VERTICAL_CONTENT_RADIUS } from '../../style/placementArrow';
|
||||
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface TourToken extends FullToken<'Tour'> {
|
||||
tourZIndexPopup: number;
|
||||
indicatorWidth: number;
|
||||
indicatorHeight: number;
|
||||
tourBorderRadius: number;
|
||||
tourCloseSize: number;
|
||||
}
|
||||
|
||||
// =============================== Base ===============================
|
||||
const genBaseStyle: GenerateStyle<TourToken> = token => {
|
||||
const {
|
||||
componentCls,
|
||||
lineHeight,
|
||||
padding,
|
||||
paddingXS,
|
||||
borderRadius,
|
||||
borderRadiusXS,
|
||||
colorPrimary,
|
||||
colorText,
|
||||
colorFill,
|
||||
indicatorHeight,
|
||||
indicatorWidth,
|
||||
boxShadowTertiary,
|
||||
tourZIndexPopup,
|
||||
fontSize,
|
||||
colorBgContainer,
|
||||
fontWeightStrong,
|
||||
marginXS,
|
||||
colorTextLightSolid,
|
||||
tourBorderRadius,
|
||||
colorWhite,
|
||||
colorBgTextHover,
|
||||
tourCloseSize,
|
||||
motionDurationSlow,
|
||||
antCls,
|
||||
} = token;
|
||||
|
||||
return [
|
||||
{
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
|
||||
color: colorText,
|
||||
position: 'absolute',
|
||||
zIndex: tourZIndexPopup,
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
fontSize,
|
||||
lineHeight,
|
||||
width: 520,
|
||||
'--antd-arrow-background-color': colorBgContainer,
|
||||
|
||||
'&-pure': {
|
||||
maxWidth: '100%',
|
||||
position: 'relative',
|
||||
},
|
||||
|
||||
[`&${componentCls}-hidden`]: {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
// ============================= panel content ============================
|
||||
[`${componentCls}-content`]: {
|
||||
position: 'relative',
|
||||
},
|
||||
[`${componentCls}-inner`]: {
|
||||
textAlign: 'start',
|
||||
textDecoration: 'none',
|
||||
borderRadius: tourBorderRadius,
|
||||
boxShadow: boxShadowTertiary,
|
||||
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.motionDurationMid}, color ${token.motionDurationMid}`,
|
||||
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',
|
||||
[`${componentCls}-indicators`]: {
|
||||
display: 'inline-block',
|
||||
|
||||
[`${componentCls}-indicator`]: {
|
||||
width: indicatorWidth,
|
||||
height: indicatorHeight,
|
||||
display: 'inline-block',
|
||||
borderRadius: '50%',
|
||||
background: colorFill,
|
||||
'&:not(:last-child)': {
|
||||
marginInlineEnd: indicatorHeight,
|
||||
},
|
||||
'&-active': {
|
||||
background: colorPrimary,
|
||||
},
|
||||
},
|
||||
},
|
||||
[`${componentCls}-buttons`]: {
|
||||
marginInlineStart: 'auto',
|
||||
[`${antCls}-btn`]: {
|
||||
marginInlineStart: marginXS,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ============================= primary type ===========================
|
||||
// `$` for panel, `&$` for pure panel
|
||||
[`${componentCls}-primary, &${componentCls}-primary`]: {
|
||||
'--antd-arrow-background-color': colorPrimary,
|
||||
|
||||
[`${componentCls}-inner`]: {
|
||||
color: colorTextLightSolid,
|
||||
textAlign: 'start',
|
||||
textDecoration: 'none',
|
||||
backgroundColor: colorPrimary,
|
||||
borderRadius,
|
||||
boxShadow: boxShadowTertiary,
|
||||
|
||||
[`${componentCls}-close`]: {
|
||||
color: colorTextLightSolid,
|
||||
},
|
||||
|
||||
[`${componentCls}-indicators`]: {
|
||||
[`${componentCls}-indicator`]: {
|
||||
background: new TinyColor(colorTextLightSolid).setAlpha(0.15).toRgbString(),
|
||||
'&-active': {
|
||||
background: colorTextLightSolid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-prev-btn`]: {
|
||||
color: colorTextLightSolid,
|
||||
borderColor: new TinyColor(colorTextLightSolid).setAlpha(0.15).toRgbString(),
|
||||
backgroundColor: colorPrimary,
|
||||
|
||||
'&: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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ============================= mask ===========================
|
||||
[`${componentCls}-mask`]: {
|
||||
[`${componentCls}-placeholder-animated`]: {
|
||||
transition: `all ${motionDurationSlow}`,
|
||||
},
|
||||
},
|
||||
|
||||
// =========== Limit left and right placement radius ==============
|
||||
[[
|
||||
'&-placement-left',
|
||||
'&-placement-leftTop',
|
||||
'&-placement-leftBottom',
|
||||
'&-placement-right',
|
||||
'&-placement-rightTop',
|
||||
'&-placement-rightBottom',
|
||||
].join(',')]: {
|
||||
[`${componentCls}-inner`]: {
|
||||
borderRadius: Math.min(tourBorderRadius, MAX_VERTICAL_CONTENT_RADIUS),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ============================= Arrow ===========================
|
||||
getArrowStyle<TourToken>(token, {
|
||||
colorBg: 'var(--antd-arrow-background-color)',
|
||||
contentRadius: tourBorderRadius,
|
||||
limitVerticalRadius: true,
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genComponentStyleHook('Tour', token => {
|
||||
const { borderRadiusLG, fontSize, lineHeight } = token;
|
||||
const TourToken = mergeToken<TourToken>(token, {
|
||||
tourZIndexPopup: token.zIndexPopupBase + 70,
|
||||
indicatorWidth: 6,
|
||||
indicatorHeight: 6,
|
||||
tourBorderRadius: borderRadiusLG,
|
||||
tourCloseSize: fontSize * lineHeight,
|
||||
});
|
||||
return [genBaseStyle(TourToken)];
|
||||
});
|
35
components/tour/useMergedType.ts
Normal file
35
components/tour/useMergedType.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import useMergedState from '../_util/hooks/useMergedState';
|
||||
import type { TourProps } from './interface';
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
|
||||
interface Props {
|
||||
defaultType?: string;
|
||||
steps?: TourProps['steps'];
|
||||
current?: Ref<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,
|
||||
});
|
||||
|
||||
watch(current, val => {
|
||||
if (val === undefined) return;
|
||||
updateInnerCurrent(val);
|
||||
});
|
||||
|
||||
const innerType = computed(() => {
|
||||
return typeof innerCurrent.value === 'number' ? steps[innerCurrent.value]?.type : defaultType;
|
||||
});
|
||||
|
||||
const currentMergedType = computed(() => innerType.value ?? defaultType);
|
||||
|
||||
return { currentMergedType, updateInnerCurrent };
|
||||
};
|
||||
|
||||
export default useMergedType;
|
130
components/vc-tour/Mask.tsx
Normal file
130
components/vc-tour/Mask.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import type { PosInfo } from './hooks/useTarget';
|
||||
import useId from '../_util/hooks/useId';
|
||||
import Portal from '../_util/PortalWrapper';
|
||||
import { someType, objectType, booleanType } from '../_util/type';
|
||||
|
||||
const COVER_PROPS = {
|
||||
fill: 'transparent',
|
||||
pointerEvents: 'auto',
|
||||
};
|
||||
|
||||
export interface MaskProps {
|
||||
prefixCls?: string;
|
||||
pos: PosInfo; // 获取引导卡片指向的元素
|
||||
rootClassName?: string;
|
||||
showMask?: boolean;
|
||||
style?: CSSProperties;
|
||||
fill?: string;
|
||||
open?: boolean;
|
||||
animated?: boolean | { placeholder: boolean };
|
||||
zIndex?: number;
|
||||
}
|
||||
const Mask = defineComponent({
|
||||
name: 'Mask',
|
||||
props: {
|
||||
prefixCls: { type: String },
|
||||
pos: objectType<PosInfo>(), // 获取引导卡片指向的元素
|
||||
rootClassName: { type: String },
|
||||
showMask: booleanType(),
|
||||
fill: { type: String, default: 'rgba(0,0,0,0.5)' },
|
||||
open: booleanType(),
|
||||
animated: someType<boolean | { placeholder: boolean }>([Boolean, Object]),
|
||||
zIndex: { type: Number },
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
return () => {
|
||||
const { prefixCls, open, rootClassName, pos, showMask, fill, animated, zIndex } = props;
|
||||
|
||||
const id = useId();
|
||||
const maskId = `${prefixCls}-mask-${id}`;
|
||||
const mergedAnimated = typeof animated === 'object' ? animated?.placeholder : animated;
|
||||
|
||||
console.log(open);
|
||||
return (
|
||||
<Portal
|
||||
visible={open}
|
||||
v-slots={{
|
||||
default: () =>
|
||||
open && (
|
||||
<div
|
||||
{...attrs}
|
||||
class={classNames(`${prefixCls}-mask`, rootClassName, attrs.class)}
|
||||
style={[
|
||||
{
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{showMask ? (
|
||||
<svg
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<mask id={maskId}>
|
||||
<rect x="0" y="0" width="100vw" height="100vh" fill="white" />
|
||||
{pos && (
|
||||
<rect
|
||||
x={pos.left}
|
||||
y={pos.top}
|
||||
rx={pos.radius}
|
||||
width={pos.width}
|
||||
height={pos.height}
|
||||
fill="black"
|
||||
class={mergedAnimated ? `${prefixCls}-placeholder-animated` : ''}
|
||||
/>
|
||||
)}
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill={fill}
|
||||
mask={`url(#${maskId})`}
|
||||
/>
|
||||
|
||||
{/* Block click region */}
|
||||
{pos && (
|
||||
<>
|
||||
<rect {...COVER_PROPS} x="0" y="0" width="100%" height={pos.top} />
|
||||
<rect {...COVER_PROPS} x="0" y="0" width={pos.left} height="100%" />
|
||||
<rect
|
||||
{...COVER_PROPS}
|
||||
x="0"
|
||||
y={pos.top + pos.height}
|
||||
width="100%"
|
||||
height={`calc(100vh - ${pos.top + pos.height}px)`}
|
||||
/>
|
||||
<rect
|
||||
{...COVER_PROPS}
|
||||
x={pos.left + pos.width}
|
||||
y="0"
|
||||
width={`calc(100vw - ${pos.left + pos.width}px)`}
|
||||
height="100%"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Mask;
|
243
components/vc-tour/Tour.tsx
Normal file
243
components/vc-tour/Tour.tsx
Normal file
@ -0,0 +1,243 @@
|
||||
import { ref, computed, watch, watchEffect, defineComponent, toRefs, shallowRef } from 'vue';
|
||||
import type { CSSProperties, ExtractPropTypes } from 'vue';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import Trigger, { triggerProps } from '../vc-trigger';
|
||||
import classNames from '../_util/classNames';
|
||||
import useMergedState from '../_util/hooks/useMergedState';
|
||||
import useTarget from './hooks/useTarget';
|
||||
import type { Gap } from './hooks/useTarget';
|
||||
import TourStep from './TourStep';
|
||||
import type { TourStepInfo, TourStepProps } from './interface';
|
||||
import Mask from './Mask';
|
||||
import { getPlacements } from './placements';
|
||||
import type { PlacementType } from './placements';
|
||||
import { initDefaultProps } from '../_util/props-util';
|
||||
import useScrollLocker from './hooks/useScrollLocker';
|
||||
import canUseDom from '../_util/canUseDom';
|
||||
import {
|
||||
someType,
|
||||
stringType,
|
||||
arrayType,
|
||||
objectType,
|
||||
functionType,
|
||||
booleanType,
|
||||
} from '../_util/type';
|
||||
|
||||
const CENTER_PLACEHOLDER: CSSProperties = {
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
width: 1,
|
||||
height: 1,
|
||||
};
|
||||
|
||||
export const tourProps = () => {
|
||||
const { builtinPlacements, ...pickedTriggerProps } = triggerProps();
|
||||
return {
|
||||
...pickedTriggerProps,
|
||||
steps: arrayType<TourStepInfo[]>(),
|
||||
open: booleanType(),
|
||||
defaultCurrent: { type: Number },
|
||||
current: { type: Number },
|
||||
onChange: functionType<(current: number) => void>(),
|
||||
onClose: functionType<(current: number) => void>(),
|
||||
onFinish: functionType<() => void>(),
|
||||
mask: someType<boolean | { style?: CSSProperties; color?: string }>([Boolean, Object], true),
|
||||
arrow: someType<boolean | { pointAtCenter: boolean }>([Boolean, Object], true),
|
||||
rootClassName: { type: String },
|
||||
placement: stringType<PlacementType>('bottom'),
|
||||
prefixCls: { type: String, default: 'rc-tour' },
|
||||
renderPanel: functionType<(props: TourStepProps, current: number) => VueNode>(),
|
||||
gap: objectType<Gap>(),
|
||||
animated: someType<boolean | { placeholder: boolean }>([Boolean, Object]),
|
||||
scrollIntoViewOptions: someType<boolean | ScrollIntoViewOptions>([Boolean, Object], true),
|
||||
zIndex: { type: Number, default: 1001 },
|
||||
};
|
||||
};
|
||||
|
||||
export type TourProps = Partial<ExtractPropTypes<ReturnType<typeof tourProps>>>;
|
||||
|
||||
const Tour = defineComponent({
|
||||
name: 'Tour',
|
||||
props: initDefaultProps(tourProps(), {}),
|
||||
setup(props) {
|
||||
const { defaultCurrent, placement, mask, scrollIntoViewOptions, open, gap, arrow } =
|
||||
toRefs(props);
|
||||
|
||||
const triggerRef = ref();
|
||||
|
||||
const [mergedCurrent, setMergedCurrent] = useMergedState(0, {
|
||||
value: computed(() => props.current),
|
||||
defaultValue: defaultCurrent.value,
|
||||
});
|
||||
|
||||
const [mergedOpen, setMergedOpen] = useMergedState(undefined, {
|
||||
value: computed(() => props.open),
|
||||
postState: origin =>
|
||||
mergedCurrent.value < 0 || mergedCurrent.value >= props.steps.length
|
||||
? false
|
||||
: origin ?? true,
|
||||
});
|
||||
|
||||
const openRef = shallowRef(mergedOpen.value);
|
||||
watchEffect(() => {
|
||||
if (mergedOpen.value && !openRef.value) {
|
||||
setMergedCurrent(0);
|
||||
}
|
||||
openRef.value = mergedOpen.value;
|
||||
});
|
||||
|
||||
const curStep = computed(() => (props.steps[mergedCurrent.value] || {}) as TourStepInfo);
|
||||
|
||||
const mergedPlacement = computed(() => curStep.value.placement ?? placement.value);
|
||||
const mergedMask = computed(() => mergedOpen.value && (curStep.value.mask ?? mask.value));
|
||||
const mergedScrollIntoViewOptions = computed(
|
||||
() => curStep.value.scrollIntoViewOptions ?? scrollIntoViewOptions.value,
|
||||
);
|
||||
const [posInfo, targetElement] = useTarget(
|
||||
computed(() => curStep.value.target),
|
||||
open,
|
||||
gap,
|
||||
mergedScrollIntoViewOptions,
|
||||
);
|
||||
|
||||
// ========================= arrow =========================
|
||||
const mergedArrow = computed(() =>
|
||||
targetElement.value
|
||||
? typeof curStep.value.arrow === 'undefined'
|
||||
? arrow.value
|
||||
: curStep.value.arrow
|
||||
: false,
|
||||
);
|
||||
const arrowPointAtCenter = computed(() =>
|
||||
typeof mergedArrow.value === 'object' ? mergedArrow.value.pointAtCenter : false,
|
||||
);
|
||||
|
||||
watch(arrowPointAtCenter, () => {
|
||||
triggerRef.value?.forcePopupAlign();
|
||||
});
|
||||
watch(mergedCurrent, () => {
|
||||
triggerRef.value?.forcePopupAlign();
|
||||
});
|
||||
|
||||
// ========================= Change =========================
|
||||
const onInternalChange = (nextCurrent: number) => {
|
||||
setMergedCurrent(nextCurrent);
|
||||
props.onChange?.(nextCurrent);
|
||||
};
|
||||
|
||||
// ========================= lock scroll =========================
|
||||
const lockScroll = computed(() => mergedOpen.value && canUseDom());
|
||||
|
||||
useScrollLocker(lockScroll);
|
||||
|
||||
return () => {
|
||||
const {
|
||||
prefixCls,
|
||||
steps,
|
||||
onClose,
|
||||
onFinish,
|
||||
rootClassName,
|
||||
renderPanel,
|
||||
animated,
|
||||
zIndex,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// ========================= Render =========================
|
||||
// Skip if not init yet
|
||||
if (targetElement.value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setMergedOpen(false);
|
||||
onClose?.(mergedCurrent.value);
|
||||
};
|
||||
|
||||
const mergedShowMask =
|
||||
typeof mergedMask.value === 'boolean' ? mergedMask.value : !!mergedMask.value;
|
||||
const mergedMaskStyle = typeof mergedMask.value === 'boolean' ? undefined : mergedMask.value;
|
||||
|
||||
// when targetElement is not exist, use body as triggerDOMNode
|
||||
const getTriggerDOMNode = () => {
|
||||
return targetElement.value || document.body;
|
||||
};
|
||||
|
||||
const getPopupElement = () => (
|
||||
<TourStep
|
||||
arrow={mergedArrow.value}
|
||||
key="content"
|
||||
prefixCls={prefixCls}
|
||||
total={steps.length}
|
||||
renderPanel={renderPanel}
|
||||
onPrev={() => {
|
||||
onInternalChange(mergedCurrent.value - 1);
|
||||
}}
|
||||
onNext={() => {
|
||||
onInternalChange(mergedCurrent.value + 1);
|
||||
}}
|
||||
onClose={handleClose}
|
||||
current={mergedCurrent.value}
|
||||
onFinish={() => {
|
||||
handleClose();
|
||||
onFinish?.();
|
||||
}}
|
||||
{...curStep.value}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Mask
|
||||
zIndex={zIndex}
|
||||
prefixCls={prefixCls}
|
||||
pos={posInfo.value}
|
||||
showMask={mergedShowMask}
|
||||
style={mergedMaskStyle?.style}
|
||||
fill={mergedMaskStyle?.color}
|
||||
open={mergedOpen.value}
|
||||
animated={animated}
|
||||
rootClassName={rootClassName}
|
||||
/>
|
||||
<Trigger
|
||||
builtinPlacements={getPlacements(arrowPointAtCenter.value)}
|
||||
{...restProps}
|
||||
ref={triggerRef}
|
||||
popupStyle={
|
||||
!curStep.value.target
|
||||
? {
|
||||
...curStep.value.style,
|
||||
position: 'fixed',
|
||||
left: CENTER_PLACEHOLDER.left,
|
||||
top: CENTER_PLACEHOLDER.top,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}
|
||||
: curStep.value.style
|
||||
}
|
||||
popupPlacement={!curStep.value.target ? 'center' : mergedPlacement.value}
|
||||
popupVisible={mergedOpen.value}
|
||||
popupClassName={classNames(rootClassName, curStep.value.className)}
|
||||
prefixCls={prefixCls}
|
||||
popup={getPopupElement}
|
||||
forceRender={false}
|
||||
destroyPopupOnHide
|
||||
zIndex={zIndex}
|
||||
mask={false}
|
||||
getTriggerDOMNode={getTriggerDOMNode}
|
||||
>
|
||||
<div
|
||||
class={classNames(rootClassName, `${prefixCls}-target-placeholder`)}
|
||||
style={{
|
||||
...(posInfo.value || CENTER_PLACEHOLDER),
|
||||
position: 'fixed',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
</Trigger>
|
||||
</>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default Tour;
|
55
components/vc-tour/TourStep/DefaultPanel.tsx
Normal file
55
components/vc-tour/TourStep/DefaultPanel.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import classNames from '../../_util/classNames';
|
||||
import { tourStepProps } from '../interface';
|
||||
import type { TourStepProps } from '../interface';
|
||||
|
||||
const DefaultPanel = defineComponent({
|
||||
name: 'DefaultPanel',
|
||||
props: tourStepProps(),
|
||||
setup(props, { attrs }) {
|
||||
return () => {
|
||||
const { prefixCls, current, total, title, description, onClose, onPrev, onNext, onFinish } =
|
||||
props as TourStepProps;
|
||||
return (
|
||||
<div {...attrs} class={classNames(`${prefixCls}-content`, attrs.class)}>
|
||||
<div class={`${prefixCls}-inner`}>
|
||||
<button type="button" onClick={onClose} aria-label="Close" class={`${prefixCls}-close`}>
|
||||
<span class={`${prefixCls}-close-x`}>×</span>
|
||||
</button>
|
||||
<div class={`${prefixCls}-header`}>
|
||||
<div class={`${prefixCls}-title`}>{title}</div>
|
||||
</div>
|
||||
<div class={`${prefixCls}-description`}>{description}</div>
|
||||
<div class={`${prefixCls}-footer`}>
|
||||
<div class={`${prefixCls}-sliders`}>
|
||||
{total > 1
|
||||
? [...Array.from({ length: total }).keys()].map((item, index) => {
|
||||
return <span key={item} class={index === current ? 'active' : ''} />;
|
||||
})
|
||||
: null}
|
||||
</div>
|
||||
<div class={`${prefixCls}-buttons`}>
|
||||
{current !== 0 ? (
|
||||
<button class={`${prefixCls}-prev-btn`} onClick={onPrev}>
|
||||
Prev
|
||||
</button>
|
||||
) : null}
|
||||
{current === total - 1 ? (
|
||||
<button class={`${prefixCls}-finish-btn`} onClick={onFinish}>
|
||||
Finish
|
||||
</button>
|
||||
) : (
|
||||
<button class={`${prefixCls}-next-btn`} onClick={onNext}>
|
||||
Next
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default DefaultPanel;
|
25
components/vc-tour/TourStep/index.tsx
Normal file
25
components/vc-tour/TourStep/index.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import DefaultPanel from './DefaultPanel';
|
||||
import { tourStepProps } from '../interface';
|
||||
|
||||
const TourStep = defineComponent({
|
||||
name: 'TourStep',
|
||||
props: tourStepProps(),
|
||||
setup(props, { attrs }) {
|
||||
return () => {
|
||||
const { current, renderPanel } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{typeof renderPanel === 'function' ? (
|
||||
renderPanel({ ...attrs, ...props }, current)
|
||||
) : (
|
||||
<DefaultPanel {...attrs} {...props} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default TourStep;
|
44
components/vc-tour/hooks/useScrollLocker.tsx
Normal file
44
components/vc-tour/hooks/useScrollLocker.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, watchEffect } from 'vue';
|
||||
import { updateCSS, removeCSS } from '../../vc-util/Dom/dynamicCSS';
|
||||
import getScrollBarSize from '../../_util/getScrollBarSize';
|
||||
|
||||
const UNIQUE_ID = `vc-util-locker-${Date.now()}`;
|
||||
|
||||
let uuid = 0;
|
||||
|
||||
/**../vc-util/Dom/dynam
|
||||
* Test usage export. Do not use in your production
|
||||
*/
|
||||
export function isBodyOverflowing() {
|
||||
return (
|
||||
document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
window.innerWidth > document.body.offsetWidth
|
||||
);
|
||||
}
|
||||
|
||||
export default function useScrollLocker(lock?: Ref<boolean>) {
|
||||
const mergedLock = computed(() => !!lock && !!lock.value);
|
||||
const id = computed(() => {
|
||||
uuid += 1;
|
||||
return `${UNIQUE_ID}_${uuid}`;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (mergedLock.value) {
|
||||
const scrollbarSize = getScrollBarSize();
|
||||
const isOverflow = isBodyOverflowing();
|
||||
|
||||
updateCSS(
|
||||
`
|
||||
html body {
|
||||
overflow-y: hidden;
|
||||
${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''}
|
||||
}`,
|
||||
id.value,
|
||||
);
|
||||
} else {
|
||||
removeCSS(id.value);
|
||||
}
|
||||
});
|
||||
}
|
100
components/vc-tour/hooks/useTarget.ts
Normal file
100
components/vc-tour/hooks/useTarget.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { computed, watchEffect, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { isInViewPort } from '../util';
|
||||
import type { TourStepInfo } from '..';
|
||||
|
||||
import useState from '../../_util/hooks/useState';
|
||||
|
||||
export interface Gap {
|
||||
offset?: number;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export interface PosInfo {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
radius: number;
|
||||
}
|
||||
|
||||
export default function useTarget(
|
||||
target: Ref<TourStepInfo['target']>,
|
||||
open: Ref<boolean>,
|
||||
gap?: Ref<Gap>,
|
||||
scrollIntoViewOptions?: Ref<boolean | ScrollIntoViewOptions>,
|
||||
): [Ref<PosInfo>, Ref<HTMLElement>] {
|
||||
// ========================= Target =========================
|
||||
// We trade `undefined` as not get target by function yet.
|
||||
// `null` as empty target.
|
||||
const [targetElement, setTargetElement] = useState<null | HTMLElement | undefined>(undefined);
|
||||
|
||||
watchEffect(() => {
|
||||
const nextElement = typeof target.value === 'function' ? (target.value as any)() : target.value;
|
||||
|
||||
setTargetElement(nextElement || null);
|
||||
});
|
||||
|
||||
// ========================= Align ==========================
|
||||
const [posInfo, setPosInfo] = useState<PosInfo>(null);
|
||||
|
||||
const updatePos = () => {
|
||||
if (targetElement.value) {
|
||||
// Exist target element. We should scroll and get target position
|
||||
if (!isInViewPort(targetElement.value) && open.value) {
|
||||
targetElement.value.scrollIntoView(scrollIntoViewOptions.value);
|
||||
}
|
||||
|
||||
const { left, top, width, height } = targetElement.value.getBoundingClientRect();
|
||||
const nextPosInfo: PosInfo = { left, top, width, height, radius: 0 };
|
||||
|
||||
setPosInfo(nextPosInfo);
|
||||
} else {
|
||||
// Not exist target which means we just show in center
|
||||
setPosInfo(null);
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
updatePos();
|
||||
// update when window resize
|
||||
window.addEventListener('resize', updatePos);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePos);
|
||||
};
|
||||
});
|
||||
|
||||
watch(
|
||||
open,
|
||||
val => {
|
||||
updatePos();
|
||||
// update when window resize
|
||||
if (val) {
|
||||
window.addEventListener('resize', updatePos);
|
||||
} else {
|
||||
window.removeEventListener('resize', updatePos);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// ======================== PosInfo =========================
|
||||
const mergedPosInfo = computed(() => {
|
||||
if (!posInfo.value) {
|
||||
return posInfo.value;
|
||||
}
|
||||
|
||||
const gapOffset = gap.value?.offset || 6;
|
||||
const gapRadius = gap.value?.radius || 2;
|
||||
|
||||
return {
|
||||
left: posInfo.value.left - gapOffset,
|
||||
top: posInfo.value.top - gapOffset,
|
||||
width: posInfo.value.width + gapOffset * 2,
|
||||
height: posInfo.value.height + gapOffset * 2,
|
||||
radius: gapRadius,
|
||||
};
|
||||
});
|
||||
|
||||
return [mergedPosInfo, targetElement];
|
||||
}
|
6
components/vc-tour/index.tsx
Normal file
6
components/vc-tour/index.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import Tour from './Tour';
|
||||
export type { TourProps } from './Tour';
|
||||
export { tourProps } from './Tour';
|
||||
export type { TourStepInfo, TourStepProps } from './interface';
|
||||
export { tourStepInfo, tourStepProps } from './interface';
|
||||
export default Tour;
|
36
components/vc-tour/interface.ts
Normal file
36
components/vc-tour/interface.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { ExtractPropTypes, CSSProperties } from 'vue';
|
||||
import type { PlacementType } from './placements';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import { someType, stringType, objectType, functionType } from '../_util/type';
|
||||
|
||||
export const tourStepInfo = () => ({
|
||||
arrow: someType<boolean | { pointAtCenter: boolean }>([Boolean, Object]),
|
||||
target: someType<HTMLElement | (() => HTMLElement) | null | (() => null)>([
|
||||
String,
|
||||
Function,
|
||||
Object,
|
||||
]),
|
||||
title: someType<string | VueNode>([String, Object]),
|
||||
description: someType<string | VueNode>([String, Object]),
|
||||
placement: stringType<PlacementType>(),
|
||||
mask: someType<boolean | { style?: CSSProperties; color?: string }>([Object, Boolean], true),
|
||||
className: { type: String },
|
||||
style: objectType<CSSProperties>(),
|
||||
scrollIntoViewOptions: someType<boolean | ScrollIntoViewOptions>([Boolean, Object]),
|
||||
});
|
||||
|
||||
export type TourStepInfo = Partial<ExtractPropTypes<ReturnType<typeof tourStepInfo>>>;
|
||||
|
||||
export const tourStepProps = () => ({
|
||||
...tourStepInfo(),
|
||||
prefixCls: { type: String },
|
||||
total: { type: Number },
|
||||
current: { type: Number },
|
||||
onClose: functionType<(e: MouseEvent) => void>(),
|
||||
onFinish: functionType<(e: MouseEvent) => void>(),
|
||||
renderPanel: functionType<(step: any, current: number) => VueNode>(),
|
||||
onPrev: functionType<(e: MouseEvent) => void>(),
|
||||
onNext: functionType<(e: MouseEvent) => void>(),
|
||||
});
|
||||
|
||||
export type TourStepProps = Partial<ExtractPropTypes<ReturnType<typeof tourStepProps>>>;
|
135
components/vc-tour/placements.tsx
Normal file
135
components/vc-tour/placements.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
export type PlacementType =
|
||||
| 'left'
|
||||
| 'leftTop'
|
||||
| 'leftBottom'
|
||||
| 'right'
|
||||
| 'rightTop'
|
||||
| 'rightBottom'
|
||||
| 'top'
|
||||
| 'topLeft'
|
||||
| 'topRight'
|
||||
| 'bottom'
|
||||
| 'bottomLeft'
|
||||
| 'bottomRight'
|
||||
| 'center';
|
||||
|
||||
const targetOffset = [0, 0];
|
||||
|
||||
export type AlignPointTopBottom = 't' | 'b' | 'c';
|
||||
export type AlignPointLeftRight = 'l' | 'r' | 'c';
|
||||
|
||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
||||
export type AlignPoint = `${AlignPointTopBottom}${AlignPointLeftRight}`;
|
||||
|
||||
export interface AlignType {
|
||||
/**
|
||||
* move point of source node to align with point of target node.
|
||||
* Such as ['tr','cc'], align top right point of source node with center point of target node.
|
||||
* Point can be 't'(top), 'b'(bottom), 'c'(center), 'l'(left), 'r'(right) */
|
||||
points?: (string | AlignPoint)[];
|
||||
/**
|
||||
* offset source node by offset[0] in x and offset[1] in y.
|
||||
* If offset contains percentage string value, it is relative to sourceNode region.
|
||||
*/
|
||||
offset?: number[];
|
||||
/**
|
||||
* offset target node by offset[0] in x and offset[1] in y.
|
||||
* If targetOffset contains percentage string value, it is relative to targetNode region.
|
||||
*/
|
||||
targetOffset?: number[];
|
||||
/**
|
||||
* If adjustX field is true, will adjust source node in x direction if source node is invisible.
|
||||
* If adjustY field is true, will adjust source node in y direction if source node is invisible.
|
||||
*/
|
||||
overflow?: {
|
||||
adjustX?: boolean | number;
|
||||
adjustY?: boolean | number;
|
||||
shiftX?: boolean | number;
|
||||
shiftY?: boolean | number;
|
||||
};
|
||||
/** Auto adjust arrow position */
|
||||
autoArrow?: boolean;
|
||||
/**
|
||||
* Config visible region check of html node. Default `visible`:
|
||||
* - `visible`: The visible region of user browser window. Use `clientHeight` for check.
|
||||
* - `scroll`: The whole region of the html scroll area. Use `scrollHeight` for check.
|
||||
*/
|
||||
htmlRegion?: 'visible' | 'scroll';
|
||||
/**
|
||||
* Whether use css right instead of left to position
|
||||
*/
|
||||
useCssRight?: boolean;
|
||||
/**
|
||||
* Whether use css bottom instead of top to position
|
||||
*/
|
||||
useCssBottom?: boolean;
|
||||
/**
|
||||
* Whether use css transform instead of left/top/right/bottom to position if browser supports.
|
||||
* Defaults to false.
|
||||
*/
|
||||
useCssTransform?: boolean;
|
||||
ignoreShake?: boolean;
|
||||
}
|
||||
|
||||
export type BuildInPlacements = Record<string, AlignType>;
|
||||
|
||||
const basePlacements: BuildInPlacements = {
|
||||
left: {
|
||||
points: ['cr', 'cl'],
|
||||
offset: [-8, 0],
|
||||
},
|
||||
right: {
|
||||
points: ['cl', 'cr'],
|
||||
offset: [8, 0],
|
||||
},
|
||||
top: {
|
||||
points: ['bc', 'tc'],
|
||||
offset: [0, -8],
|
||||
},
|
||||
bottom: {
|
||||
points: ['tc', 'bc'],
|
||||
offset: [0, 8],
|
||||
},
|
||||
topLeft: {
|
||||
points: ['bl', 'tl'],
|
||||
offset: [0, -8],
|
||||
},
|
||||
leftTop: {
|
||||
points: ['tr', 'tl'],
|
||||
offset: [-8, 0],
|
||||
},
|
||||
topRight: {
|
||||
points: ['br', 'tr'],
|
||||
offset: [0, -8],
|
||||
},
|
||||
rightTop: {
|
||||
points: ['tl', 'tr'],
|
||||
offset: [8, 0],
|
||||
},
|
||||
bottomRight: {
|
||||
points: ['tr', 'br'],
|
||||
offset: [0, 8],
|
||||
},
|
||||
rightBottom: {
|
||||
points: ['bl', 'br'],
|
||||
offset: [8, 0],
|
||||
},
|
||||
bottomLeft: {
|
||||
points: ['tl', 'bl'],
|
||||
offset: [0, 8],
|
||||
},
|
||||
leftBottom: {
|
||||
points: ['br', 'bl'],
|
||||
offset: [-8, 0],
|
||||
},
|
||||
};
|
||||
|
||||
export function getPlacements(arrowPointAtCenter = false) {
|
||||
const placements: BuildInPlacements = {};
|
||||
Object.keys(basePlacements).forEach(key => {
|
||||
placements[key] = { ...basePlacements[key], autoArrow: arrowPointAtCenter, targetOffset };
|
||||
});
|
||||
return placements;
|
||||
}
|
||||
|
||||
export const placements = getPlacements();
|
7
components/vc-tour/util.ts
Normal file
7
components/vc-tour/util.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function isInViewPort(element: HTMLElement) {
|
||||
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const { top, right, bottom, left } = element.getBoundingClientRect();
|
||||
|
||||
return top >= 0 && left >= 0 && right <= viewWidth && bottom <= viewHeight;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import type { CSSProperties, HTMLAttributes, PropType } from 'vue';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
import { computed, defineComponent, inject, provide, shallowRef } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { triggerProps, noop } from './interface';
|
||||
import contains from '../vc-util/Dom/contains';
|
||||
import raf from '../_util/raf';
|
||||
import {
|
||||
@ -21,17 +21,6 @@ import { cloneElement } from '../_util/vnode';
|
||||
import supportsPassive from '../_util/supportsPassive';
|
||||
import { useInjectTrigger, useProvidePortal } from './context';
|
||||
|
||||
function noop() {}
|
||||
function returnEmptyString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function returnDocument(element) {
|
||||
if (element) {
|
||||
return element.ownerDocument;
|
||||
}
|
||||
return window.document;
|
||||
}
|
||||
const ALL_HANDLERS = [
|
||||
'onClick',
|
||||
'onMousedown',
|
||||
@ -47,46 +36,7 @@ export default defineComponent({
|
||||
name: 'Trigger',
|
||||
mixins: [BaseMixin],
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
action: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).def([]),
|
||||
showAction: PropTypes.any.def([]),
|
||||
hideAction: PropTypes.any.def([]),
|
||||
getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString),
|
||||
onPopupVisibleChange: Function as PropType<(open: boolean) => void>,
|
||||
afterPopupVisibleChange: PropTypes.func.def(noop),
|
||||
popup: PropTypes.any,
|
||||
popupStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
||||
prefixCls: PropTypes.string.def('rc-trigger-popup'),
|
||||
popupClassName: PropTypes.string.def(''),
|
||||
popupPlacement: String,
|
||||
builtinPlacements: PropTypes.object,
|
||||
popupTransitionName: String,
|
||||
popupAnimation: PropTypes.any,
|
||||
mouseEnterDelay: PropTypes.number.def(0),
|
||||
mouseLeaveDelay: PropTypes.number.def(0.1),
|
||||
zIndex: Number,
|
||||
focusDelay: PropTypes.number.def(0),
|
||||
blurDelay: PropTypes.number.def(0.15),
|
||||
getPopupContainer: Function,
|
||||
getDocument: PropTypes.func.def(returnDocument),
|
||||
forceRender: { type: Boolean, default: undefined },
|
||||
destroyPopupOnHide: { type: Boolean, default: false },
|
||||
mask: { type: Boolean, default: false },
|
||||
maskClosable: { type: Boolean, default: true },
|
||||
// onPopupAlign: PropTypes.func.def(noop),
|
||||
popupAlign: PropTypes.object.def(() => ({})),
|
||||
popupVisible: { type: Boolean, default: undefined },
|
||||
defaultPopupVisible: { type: Boolean, default: false },
|
||||
maskTransitionName: String,
|
||||
maskAnimation: String,
|
||||
stretch: String,
|
||||
alignPoint: { type: Boolean, default: undefined }, // Maybe we can support user pass position in the future
|
||||
autoDestroy: { type: Boolean, default: false },
|
||||
mobile: Object,
|
||||
getTriggerDOMNode: Function as PropType<(d?: HTMLElement) => HTMLElement>,
|
||||
// portal context will change
|
||||
tryPopPortal: Boolean, // no need reactive
|
||||
},
|
||||
props: triggerProps(),
|
||||
setup(props) {
|
||||
const align = computed(() => {
|
||||
const { popupPlacement, popupAlign, builtinPlacements } = props;
|
||||
|
@ -1,3 +1,8 @@
|
||||
// based on rc-trigger 5.2.10
|
||||
import Trigger from './Trigger';
|
||||
import { triggerProps } from './interface';
|
||||
import type { TriggerProps } from './interface';
|
||||
|
||||
export { triggerProps };
|
||||
export type { TriggerProps };
|
||||
export default Trigger;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { CSSProperties, TransitionProps } from 'vue';
|
||||
import type { CSSProperties, ExtractPropTypes, TransitionProps, PropType } from 'vue';
|
||||
import type { VueNode } from '../_util/type';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
/** Two char of 't' 'b' 'c' 'l' 'r'. Example: 'lt' */
|
||||
export type AlignPoint = string;
|
||||
@ -70,3 +71,59 @@ export interface MobileConfig {
|
||||
popupStyle?: CSSProperties;
|
||||
popupRender?: (originNode: VueNode) => VueNode;
|
||||
}
|
||||
|
||||
function returnEmptyString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function returnDocument(element) {
|
||||
if (element) {
|
||||
return element.ownerDocument;
|
||||
}
|
||||
return window.document;
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export const triggerProps = () => ({
|
||||
action: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).def([]),
|
||||
showAction: PropTypes.any.def([]),
|
||||
hideAction: PropTypes.any.def([]),
|
||||
getPopupClassNameFromAlign: PropTypes.any.def(returnEmptyString),
|
||||
onPopupVisibleChange: Function as PropType<(open: boolean) => void>,
|
||||
afterPopupVisibleChange: PropTypes.func.def(noop),
|
||||
popup: PropTypes.any,
|
||||
popupStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
||||
prefixCls: PropTypes.string.def('rc-trigger-popup'),
|
||||
popupClassName: PropTypes.string.def(''),
|
||||
popupPlacement: String,
|
||||
builtinPlacements: PropTypes.object,
|
||||
popupTransitionName: String,
|
||||
popupAnimation: PropTypes.any,
|
||||
mouseEnterDelay: PropTypes.number.def(0),
|
||||
mouseLeaveDelay: PropTypes.number.def(0.1),
|
||||
zIndex: Number,
|
||||
focusDelay: PropTypes.number.def(0),
|
||||
blurDelay: PropTypes.number.def(0.15),
|
||||
getPopupContainer: Function,
|
||||
getDocument: PropTypes.func.def(returnDocument),
|
||||
forceRender: { type: Boolean, default: undefined },
|
||||
destroyPopupOnHide: { type: Boolean, default: false },
|
||||
mask: { type: Boolean, default: false },
|
||||
maskClosable: { type: Boolean, default: true },
|
||||
// onPopupAlign: PropTypes.func.def(noop),
|
||||
popupAlign: PropTypes.object.def(() => ({})),
|
||||
popupVisible: { type: Boolean, default: undefined },
|
||||
defaultPopupVisible: { type: Boolean, default: false },
|
||||
maskTransitionName: String,
|
||||
maskAnimation: String,
|
||||
stretch: String,
|
||||
alignPoint: { type: Boolean, default: undefined }, // Maybe we can support user pass position in the future
|
||||
autoDestroy: { type: Boolean, default: false },
|
||||
mobile: Object,
|
||||
getTriggerDOMNode: Function as PropType<(d?: HTMLElement) => HTMLElement>,
|
||||
// portal context will change
|
||||
tryPopPortal: Boolean, // no need reactive
|
||||
});
|
||||
|
||||
export type TriggerProps = Partial<ExtractPropTypes<ReturnType<typeof triggerProps>>>;
|
||||
|
Loading…
Reference in New Issue
Block a user