Browse Source
* feat v4 add tour * fix type error * sync tour from antd5.4.6 & fix type error * fix errorpull/6607/head
果冻橙
2 years ago
committed by
GitHub
44 changed files with 3943 additions and 56 deletions
@ -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; |
||||
} |
@ -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> |
||||
`; |
@ -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> |
||||
`; |
@ -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> |
||||
`; |
||||
|
@ -0,0 +1,3 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest'; |
||||
|
||||
extendTest('tour'); |
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest'; |
||||
|
||||
demoTest('tour'); |
@ -0,0 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest'; |
||||
|
||||
describe('Tooltip tour', () => { |
||||
imageDemoTest('tour'); |
||||
}); |
@ -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(); |
||||
}); |
||||
}); |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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` | - | | |
@ -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); |
@ -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` | - | | |
@ -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; |
||||
} |
@ -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; |
@ -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)]; |
||||
}); |
@ -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; |
@ -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; |
@ -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; |
@ -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; |
@ -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; |
@ -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); |
||||
} |
||||
}); |
||||
} |
@ -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]; |
||||
} |
@ -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; |
@ -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>>>; |
@ -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(); |
@ -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,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; |
||||
|
Loading…
Reference in new issue