Skip to content

Commit

Permalink
feat(comp:tour): add tour component (#1610)
Browse files Browse the repository at this point in the history
  • Loading branch information
sallerli1 committed Jul 27, 2023
1 parent b1a5801 commit 79335e3
Show file tree
Hide file tree
Showing 56 changed files with 2,105 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/components/_private/header/src/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const Header: FunctionalComponent<HeaderProps> = (props, { slots }) => {
}

const headerProps = convertProps(props)
const headerSlots = { suffix: slots.closeIcon }
const headerSlots = { suffix: slots.closeIcon, default: slots.title }

return <IxHeader {...headerProps} v-slots={headerSlots}></IxHeader>
}
Expand Down
11 changes: 11 additions & 0 deletions packages/components/config/src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,17 @@ export const defaultConfig: GlobalConfig = {
placement: 'top',
trigger: 'hover',
},
tour: {
animatable: true,
gap: { offset: 4, radius: 2 },
offset: [0, 4],
mask: true,
placement: 'bottomStart',
showArrow: true,
scrollIntoViewOptions: true,
closeOnClick: false,
closeOnEsc: true,
},
tree: {
autoHeight: false,
blocked: false,
Expand Down
15 changes: 15 additions & 0 deletions packages/components/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import type {
import type { TabsSize } from '@idux/components/tabs'
import type { TagShape } from '@idux/components/tag'
import type { TextareaAutoRows, TextareaResize } from '@idux/components/textarea'
import type { TargetGap, TourMaskOptions } from '@idux/components/tour'
import type { TreeExpandIconRenderer, TreeNode } from '@idux/components/tree'
import type { UploadFilesType, UploadIconType, UploadRequestMethod, UploadRequestOption } from '@idux/components/upload'
import type { OverlayContainerType } from '@idux/components/utils'
Expand Down Expand Up @@ -105,6 +106,7 @@ export interface GlobalConfig {
timePicker: TimePickerConfig
transfer: TransferConfig
tooltip: TooltipConfig
tour: TourConfig
tree: TreeConfig
treeSelect: TreeSelectConfig
upload: UploadConfig
Expand Down Expand Up @@ -545,6 +547,19 @@ export interface TooltipConfig {
trigger: PopperTrigger
}

export interface TourConfig {
animatable: boolean
gap: TargetGap
mask: boolean | TourMaskOptions
offset: [number, number]
overlayContainer?: string | HTMLElement
placement: PopperPlacement
showArrow: boolean
scrollIntoViewOptions: boolean | ScrollIntoViewOptions
closeOnClick: boolean
closeOnEsc: boolean
}

export interface TreeConfig {
autoHeight: boolean
blocked: boolean
Expand Down
1 change: 1 addition & 0 deletions packages/components/default.less
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
@import './tooltip/style/themes/default.less';
@import './tree/style/themes/default.less';
@import './tree-select/style/themes/default.less';
@import './tour/style/themes/default.less';
@import './typography/style/themes/default.less';
@import './upload/style/themes/default.less';
@import './watermark/style/themes/default.less';
4 changes: 4 additions & 0 deletions packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { IxTextarea } from '@idux/components/textarea'
import { IxTimePicker, IxTimeRangePicker } from '@idux/components/time-picker'
import { IxTimeline, IxTimelineItem } from '@idux/components/timeline'
import { IxTooltip } from '@idux/components/tooltip'
import { IxTour } from '@idux/components/tour'
import { IxTransfer, IxTransferList } from '@idux/components/transfer'
import { IxTree } from '@idux/components/tree'
import { IxTreeSelect } from '@idux/components/tree-select'
Expand Down Expand Up @@ -179,6 +180,7 @@ const components = [
IxTimeline,
IxTimelineItem,
IxTooltip,
IxTour,
IxTree,
IxTreeSelect,
IxUpload,
Expand Down Expand Up @@ -272,3 +274,5 @@ export * from '@idux/components/upload'
export * from '@idux/components/utils'
export * from '@idux/components/version'
export * from '@idux/components/watermark'

export * from '@idux/components/tour'
5 changes: 5 additions & 0 deletions packages/components/locales/src/langs/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ const enUS: Locale = {
separator: 'To',
placeholder: ['Start time', 'End time'],
},
tour: {
nextText: 'Next',
prevText: 'Previous',
finishText: 'Done',
},
transfer: {
toSelect: 'Available',
selected: 'Selected',
Expand Down
5 changes: 5 additions & 0 deletions packages/components/locales/src/langs/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ const zhCN: Locale = {
separator: '至',
placeholder: ['起始时间', '结束时间'],
},
tour: {
nextText: '下一步',
prevText: '上一步',
finishText: '开始体验',
},
transfer: {
toSelect: '待选',
selected: '已选',
Expand Down
7 changes: 7 additions & 0 deletions packages/components/locales/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ export interface TimeRangePickerLocale {
placeholder: [string, string]
}

export interface TourLocale {
nextText: string
prevText: string
finishText: string
}

export interface TransferLocale {
toSelect: string
selected: string
Expand Down Expand Up @@ -138,6 +144,7 @@ export interface Locale {
table: TableLocale
timePicker: TimePickerLocale
timeRangePicker: TimeRangePickerLocale
tour: TourLocale
transfer: TransferLocale
treeSelect: TreeSelectLocale
upload: UploadLocale
Expand Down
1 change: 1 addition & 0 deletions packages/components/seer.less
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
@import './timeline/style/themes/seer.less';
@import './transfer/style/themes/seer.less';
@import './tooltip/style/themes/seer.less';
@import './tour/style/themes/default.less';
@import './tree/style/themes/seer.less';
@import './tree-select/style/themes/seer.less';
@import './typography/style/themes/seer.less';
Expand Down
1 change: 1 addition & 0 deletions packages/components/style/variable/prefix.less
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
@stepper-item-prefix: ~'@{idux-prefix}-stepper-item';
@affix-prefix: ~'@{idux-prefix}-affix';
@watermark-prefix: ~'@{idux-prefix}-watermark';
@tour-prefix: ~'@{idux-prefix}-tour';

// Private
@collapse-transition-prefix: ~'@{idux-prefix}-collapse-transition';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Vitest Snapshot v1

exports[`Tour > panel render work 1`] = `"<div class=\\"ix-header ix-header-sm\\"><!----><!----><div class=\\"ix-header-content\\"><div class=\\"ix-header-title-wrapper\\"><span class=\\"ix-header-title\\">Step1</span><!----></div><!----></div><span class=\\"ix-header-suffix\\"><i class=\\"ix-icon ix-icon-close\\" role=\\"img\\" aria-label=\\"close\\"><svg viewBox=\\"0 0 1024 1024\\" focusable=\\"false\\" aria-hidden=\\"true\\" data-icon=\\"close\\"><path d=\\"M282.24 236.928 512 466.688l229.76-229.76a12.8 12.8 0 0 1 18.176 0l27.136 27.136a12.8 12.8 0 0 1 0 18.112L557.248 512l229.76 229.888a12.8 12.8 0 0 1 0 18.112l-27.072 27.136a12.8 12.8 0 0 1-18.112 0L512 557.248l-229.76 229.76a12.8 12.8 0 0 1-18.176 0l-27.136-27.072a12.8 12.8 0 0 1 0-18.112L466.688 512l-229.76-229.76a12.8 12.8 0 0 1 0-18.176l27.136-27.136a12.8 12.8 0 0 1 18.112 0z\\"></path></svg></i></span></div><div class=\\"ix-tour-panel-inner\\"><div class=\\"ix-tour-panel-description\\">this is description...</div><div class=\\"ix-tour-panel-footer\\"><div class=\\"ix-tour-panel-indicators\\">1 / 3</div><div class=\\"ix-tour-panel-buttons\\"><!----><button class=\\"ix-button ix-button-primary ix-button-xs ix-tour-panel-button\\" type=\\"button\\"><span>下一步</span><div aria-hidden=\\"true\\" class=\\"ix-wave\\"></div></button></div></div></div>"`;

exports[`Tour > render work 1`] = `
"<!--teleport start-->
<!--teleport end-->
<div class=\\"ix-tour-placeholder\\"></div>"
`;
130 changes: 130 additions & 0 deletions packages/components/tour/__tests__/tour.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { DOMWrapper, MountingOptions, flushPromises, mount } from '@vue/test-utils'

import { renderWork, wait } from '@tests'

import Tour from '../src/Tour'
import { TourProps, TourStep } from '../src/types'

const basicSteps: TourStep[] = [
{
title: 'Step1',
description: 'this is description...',
target: '.step1',
},
{
title: 'Step2',
description: 'this is description...',
target: '.step2',
},
{
title: 'Step3',
description: 'this is description...',
target: '.step3',
},
]

function expectElVisible(selector: string, visible: boolean) {
const element = document.querySelector(selector)

expect(element === null || window.getComputedStyle(element).display === 'none')[
visible ? 'toBeFalsy' : 'toBeTruthy'
]()
}

async function triggerNext(container?: string) {
const btns = document.querySelectorAll(`${container ? container + ' ' : ''}.ix-tour-panel .ix-tour-panel-button`)
const nextButton = btns.item(btns.length - 1)

await new DOMWrapper(nextButton).trigger('click')
}

describe('Tour', () => {
const TourMount = async (options?: MountingOptions<Partial<TourProps>>, divClasses?: string[]) => {
divClasses?.forEach(cls => {
const div = document.createElement('div')
div.className = cls
document.body.appendChild(div)
})

const wrapper = mount(Tour, { ...(options as MountingOptions<TourProps>) })

await wait(2000)

return wrapper
}

renderWork<TourProps>(Tour, {
props: { steps: basicSteps, visible: true },
})

test('panel render work', async () => {
await TourMount({ props: { steps: basicSteps, visible: true } })

expect(document.querySelector('.ix-tour-panel')).not.toBe(null)
expect(document.querySelector('.ix-tour-panel')?.innerHTML).toMatchSnapshot()
})

test('v-model:visible work', async () => {
const onVisibleUpdate = vi.fn()
const wrapper = await TourMount({
props: {
steps: basicSteps,
visible: false,
overlayContainer: '.visible-test',
animatable: false,
'onUpdate:visible': onVisibleUpdate,
},
})

await flushPromises()

expectElVisible('.visible-test .ix-tour-overlay', false)
expectElVisible('.visible-test .ix-tour-mask', false)

await wrapper.setProps({ visible: true })

expectElVisible('.visible-test .ix-tour-overlay', true)
expectElVisible('.visible-test .ix-tour-mask', true)

await new DOMWrapper(document.querySelector('.visible-test .ix-tour-panel .ix-header-suffix')!).trigger('click')
expect(onVisibleUpdate).toBeCalledWith(false)

await wrapper.setProps({ visible: false })

expectElVisible('.visible-test .ix-tour-overlay', false)
expectElVisible('.visible-test .ix-tour-mask', false)
})

test('v-model:activeIndex work', async () => {
const onCurrentUpdate = vi.fn()
const onChange = vi.fn()

const wrapper = await TourMount({
props: {
steps: basicSteps,
activeIndex: 0,
overlayContainer: '.active-index-test',
visible: true,
animatable: false,
'onUpdate:activeIndex': onCurrentUpdate,
onChange,
},
})

expectElVisible('.ix-tour-panel', true)
expect(document.querySelector('.active-index-test .ix-tour-panel .ix-header .ix-header-title')?.textContent).toBe(
basicSteps[0].title,
)

await wrapper.setProps({ activeIndex: 1 })
await flushPromises()

expect(document.querySelector('.active-index-test .ix-tour-panel .ix-header .ix-header-title')?.textContent).toBe(
basicSteps[1].title,
)

await triggerNext('.active-index-test')
expect(onCurrentUpdate).toBeCalledWith(2)
expect(onChange).toBeCalledWith(2, 1)
})
})
14 changes: 14 additions & 0 deletions packages/components/tour/demo/Async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 10
title:
zh: 异步使用
en: Async usage
---

## zh

通过 `beforeEnter` 以及 `afterLeave` 实现异步场景。

## en

Achive async usage via `beforeEnter` and `afterLeave`.
74 changes: 74 additions & 0 deletions packages/components/tour/demo/Async.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<template>
<div>
<IxButton @click="beginTour">Begin</IxButton>
<IxDivider />
<IxSpace>
<div ref="firstBtnRef">
<IxButton>Step1</IxButton>
</div>
<div ref="secondBtnRef">
<IxButton>Step3</IxButton>
</div>
</IxSpace>
</div>
<IxTour v-model:visible="tourVisible" :steps="steps"></IxTour>
</template>

<script setup lang="ts">
import type { TourStep } from '@idux/components/tour'
import { ref } from 'vue'
const tourVisible = ref(false)
const beginTour = () => (tourVisible.value = true)
const firstBtnRef = ref<HTMLElement>()
const secondBtnRef = ref<HTMLElement>()
let appendedElement: HTMLElement | undefined
const steps: TourStep[] = [
{
title: 'Step1',
description: 'this is description...',
target: () => firstBtnRef.value,
},
{
title: 'Step2',
description: 'this is description...',
target: () => appendedElement,
async beforeEnter() {
await new Promise(resolve => setTimeout(resolve, 500))
const div = document.createElement('div')
div.className = 'demo-tour-async__appended-element'
div.textContent = 'This Is Step 2'
document.body.appendChild(div)
appendedElement = div
},
afterLeave() {
if (appendedElement) {
document.body.removeChild(appendedElement)
}
},
},
{
title: 'Step3',
description: 'this is description...',
target: () => secondBtnRef.value,
},
]
</script>

<style lang="less">
.demo-tour-async__appended-element {
position: fixed;
z-index: 10000;
top: 600px;
left: 600px;
width: 100px;
height: 50px;
background-color: var(--ix-color-primary);
color: #fff;
}
</style>
14 changes: 14 additions & 0 deletions packages/components/tour/demo/Basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
order: 0
title:
zh: 基本使用
en: Basic usage
---

## zh

最简单的用法。

## en

The simplest usage.

0 comments on commit 79335e3

Please sign in to comment.