Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(comp:carousel): add carousel component #634

Merged
merged 1 commit into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Carousel render work 1`] = `
"<div class=\\"ix-carousel ix-carousel-horizontal\\" style=\\"height: 0px;\\">
<div class=\\"ix-carousel-slides\\" style=\\"width: 0px; left: -0px;\\">
<div class=\\"ix-carousel-slide-item\\" style=\\"width: 0px; height: 100%;\\">
<div>card2</div>
</div>
<div class=\\"ix-carousel-slide-item\\" style=\\"width: 0px; height: 100%;\\">
<div>card1</div>
</div>
<div class=\\"ix-carousel-slide-item\\" style=\\"width: 0px; height: 100%;\\">
<div>card2</div>
</div>
<div class=\\"ix-carousel-slide-item\\" style=\\"width: 0px; height: 100%;\\">
<div>card1</div>
</div>
</div>
<!---->
<ul class=\\"ix-carousel-dot ix-carousel-dot-bottom\\">
<li class=\\"ix-carousel-dot-item ix-carousel-dot-item-active\\"><button class=\\"ix-carousel-dot-item-default\\"></button></li>
<li class=\\"ix-carousel-dot-item\\"><button class=\\"ix-carousel-dot-item-default\\"></button></li>
</ul>
</div>"
`;
133 changes: 133 additions & 0 deletions packages/components/carousel/__tests__/carousel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { MountingOptions, mount } from '@vue/test-utils'
import { h } from 'vue'

import { renderWork } from '@tests'

import Carousel from '../src/Carousel'
import { CarouselProps } from '../src/types'

describe('Carousel', () => {
const sleep = (ms = 1000) => new Promise<void>(resolve => setTimeout(() => resolve(), ms))
const CarouselMount = (options?: MountingOptions<Partial<CarouselProps>>) =>
mount(Carousel, {
slots: {
default: () => [h('div', 'card1'), h('div', 'card2'), h('div', 'card3')],
},
...(options as MountingOptions<CarouselProps>),
})

renderWork<CarouselProps>(Carousel, {
props: {
autoplayTime: 100,
},
slots: {
default: () => [h('div', 'card1'), h('div', 'card2')],
},
})

test('prop showArrow work', async () => {
const onChange = jest.fn()
const wrapper = CarouselMount({ props: { showArrow: true, onChange } })

expect(wrapper.findAll('.ix-carousel-arrow').length).toBe(2)

await wrapper.find('.ix-carousel-arrow-next').trigger('click')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(onChange).toHaveBeenCalledTimes(1)

await wrapper.find('.ix-carousel-arrow-prev').trigger('click')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(onChange).toHaveBeenCalledTimes(2)

await wrapper.setProps({ showArrow: false })
expect(wrapper.find('.ix-carousel-arrow').exists()).toBeFalsy()
})

test('prop dotPlacement work', async () => {
const wrapper = CarouselMount({ props: { dotPlacement: 'none' } })

expect(wrapper.find('.ix-carousel-dot').exists()).toBeFalsy()

const placements = ['bottom', 'top', 'start', 'end']
for (const placement of placements) {
await wrapper.setProps({ dotPlacement: placement })
expect(wrapper.find('.ix-carousel-dot').classes()).toContain(`ix-carousel-dot-${placement}`)

if (['bottom', 'top'].includes(placement)) {
expect(wrapper.find('.ix-carousel').classes()).toContain('ix-carousel-horizontal')
} else {
expect(wrapper.find('.ix-carousel').classes()).toContain('ix-carousel-vertical')
}
}
})

test('prop autoplayTime work', async () => {
const wrapper = CarouselMount({ props: { autoplayTime: 100 } })

expect(wrapper.findAll('.ix-carousel-dot-item')[0].classes()).toContain('ix-carousel-dot-item-active')

await sleep(100)
expect(wrapper.findAll('.ix-carousel-dot-item')[0].classes()).not.toContain('ix-carousel-dot-item-active')
expect(wrapper.findAll('.ix-carousel-dot-item')[1].classes()).toContain('ix-carousel-dot-item-active')

await wrapper.setProps({ autoplayTime: 0 })
await sleep(100)
expect(wrapper.findAll('.ix-carousel-dot-item')[1].classes()).toContain('ix-carousel-dot-item-active')
})

test('prop trigger work', async () => {
const onChange = jest.fn()
const wrapper = CarouselMount({ props: { onChange } })

await wrapper.findAll('.ix-carousel-dot-item')[2].trigger('click')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(wrapper.findAll('.ix-carousel-dot-item')[2].classes()).toContain('ix-carousel-dot-item-active')
expect(onChange).toHaveBeenCalledTimes(1)

await wrapper.setProps({
trigger: 'hover',
})
await wrapper.find('.ix-carousel-dot-item').trigger('mouseenter')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(wrapper.find('.ix-carousel-dot-item').classes()).toContain('ix-carousel-dot-item-active')
expect(onChange).toHaveBeenCalledTimes(2)

await wrapper.findAll('.ix-carousel-dot-item')[1].trigger('mouseenter')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(wrapper.findAll('.ix-carousel-dot-item')[1].classes()).toContain('ix-carousel-dot-item-active')
expect(onChange).toHaveBeenCalledTimes(3)
})

test('prop onChange work', async () => {
const onChange = jest.fn()
const wrapper = CarouselMount({
props: { onChange, showArrow: true },
slots: {
default: () => [h('div', 'card1')],
},
})

await wrapper.find('.ix-carousel-arrow-prev').trigger('click')
expect(onChange).toHaveBeenCalledTimes(0)

await wrapper.find('.ix-carousel-arrow-next').trigger('click')
expect(onChange).toHaveBeenCalledTimes(0)
})

test('slot dot work', async () => {
const onChange = jest.fn()
const wrapper = CarouselMount({
props: { onChange },
slots: {
default: () => [h('div', 'card1'), h('div', 'card2')],
dot: () => h('div', { class: 'custom-dot' }),
},
})

expect(wrapper.findAll('.custom-dot').length === 2).toBeTruthy()

await wrapper.findAll('.custom-dot')[1].trigger('click')
await wrapper.find('.ix-carousel-slides').trigger('transitionend')
expect(onChange).toHaveBeenCalledTimes(1)
})
})
9 changes: 9 additions & 0 deletions packages/components/carousel/demo/Arrow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title:
zh: 箭头
order: 3
---

## zh

支持自定义箭头展示
29 changes: 29 additions & 0 deletions packages/components/carousel/demo/Arrow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<IxCarousel :showArrow="true">
<div class="card-item">远看泰山黑糊糊</div>
<div class="card-item">上头细来下头粗</div>
<div class="card-item">如把泰山倒过来</div>
<div class="card-item">下头细来上头粗</div>
</IxCarousel>
<IxCarousel :showArrow="true" style="margin-top: 16px">
<div class="card-item">遥远的泰山,展现出阴暗的身影</div>
<div class="card-item">厚重的基础,支撑起浅薄的高层</div>
<div class="card-item">假如某一天,有人将那乾坤颠倒</div>
<div class="card-item">陈旧的传统,必将遭逢地裂山崩</div>
<template #arrow="{ type }">
<IxIcon v-if="type === 'prev'" name="left"></IxIcon>
<IxIcon v-else name="right"></IxIcon>
</template>
</IxCarousel>
</template>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 16px;
color: #fff;
}
</style>
9 changes: 9 additions & 0 deletions packages/components/carousel/demo/Autoplay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title:
zh: 自动轮播
order: 2
---

## zh

定时切换下一张。
19 changes: 19 additions & 0 deletions packages/components/carousel/demo/Autoplay.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<IxCarousel :autoplayTime="2000">
<div class="card-item">遥远的泰山,展现出阴暗的身影</div>
<div class="card-item">厚重的基础,支撑起浅薄的高层</div>
<div class="card-item">假如某一天,有人将那乾坤颠倒</div>
<div class="card-item">陈旧的传统,必将遭逢地裂山崩</div>
</IxCarousel>
</template>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 16px;
color: #fff;
}
</style>
10 changes: 10 additions & 0 deletions packages/components/carousel/demo/Basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title:
zh: 基本使用
en: Basic usage
order: 0
---

## zh

最简单的用法。
19 changes: 19 additions & 0 deletions packages/components/carousel/demo/Basic.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<IxCarousel>
<div class="card-item">远看泰山黑糊糊</div>
<div class="card-item">上头细来下头粗</div>
<div class="card-item">如把泰山倒过来</div>
<div class="card-item">下头细来上头粗</div>
</IxCarousel>
</template>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 16px;
color: #fff;
}
</style>
9 changes: 9 additions & 0 deletions packages/components/carousel/demo/Dot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title:
zh: 面板指示点
order: 5
---

## zh

支持自定义面板指示点
34 changes: 34 additions & 0 deletions packages/components/carousel/demo/Dot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<IxCarousel ref="carouselRef">
<div v-for="item in list" :key="item" class="card-item">{{ item }}</div>
<template #dot>
<div class="custom-dot"></div>
</template>
</IxCarousel>
</template>

<script lang="ts" setup>
const list = [
'遥远的泰山,展现出阴暗的身影',
'厚重的基础,支撑起浅薄的高层',
'假如某一天,有人将那乾坤颠倒',
'陈旧的传统,必将遭逢地裂山崩',
]
</script>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 16px;
color: #fff;
}
.custom-dot {
width: 16px;
height: 3px;
background-color: #fff;
}
</style>
9 changes: 9 additions & 0 deletions packages/components/carousel/demo/DotPlacement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title:
zh: 面板指示点
order: 1
---

## zh

面板指示点的位置有4个方向。
32 changes: 32 additions & 0 deletions packages/components/carousel/demo/DotPlacement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<IxRadioGroup v-model:value="value" buttoned>
<IxRadio value="top">top</IxRadio>
<IxRadio value="bottom">bottom</IxRadio>
<IxRadio value="start">start</IxRadio>
<IxRadio value="end">end</IxRadio>
<IxRadio value="none">none</IxRadio>
</IxRadioGroup>
<IxCarousel :dotPlacement="value">
<div class="card-item">1</div>
<div class="card-item">2</div>
<div class="card-item">3</div>
<div class="card-item">4</div>
</IxCarousel>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
const value = ref('start')
</script>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 24px;
color: #fff;
}
</style>
9 changes: 9 additions & 0 deletions packages/components/carousel/demo/Trigger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title:
zh: 指示点触发方式
order: 4
---

## zh

支持设置鼠标经过指示点时触发切换
19 changes: 19 additions & 0 deletions packages/components/carousel/demo/Trigger.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<IxCarousel trigger="hover">
<div class="card-item">遥远的泰山,展现出阴暗的身影</div>
<div class="card-item">厚重的基础,支撑起浅薄的高层</div>
<div class="card-item">假如某一天,有人将那乾坤颠倒</div>
<div class="card-item">陈旧的传统,必将遭逢地裂山崩</div>
</IxCarousel>
</template>

<style lang="less" scoped>
.card-item {
height: 160px;
line-height: 160px;
background-color: #364d79;
text-align: center;
font-size: 16px;
color: #fff;
}
</style>
12 changes: 12 additions & 0 deletions packages/components/carousel/docs/Design.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
旋转木马,通常用来循环播放同一类型的交互内容。

### 什么情况下使用?

- 当有一组平级的内容。
- 当内容空间不足时,可以用走马灯的形式进行收纳,进行轮播展现。
- 常用于一组图片或卡片轮播。

### 什么情况下不使用?

- 展示一组图片或卡片,空间足够的情况下不需要用轮播图。
- 图片或卡片的内容相互有逻辑关系,不适合用轮播图。
Loading