diff --git a/packages/components/carousel/__tests__/__snapshots__/carousel.spec.ts.snap b/packages/components/carousel/__tests__/__snapshots__/carousel.spec.ts.snap new file mode 100644 index 000000000..d9aac2044 --- /dev/null +++ b/packages/components/carousel/__tests__/__snapshots__/carousel.spec.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Carousel render work 1`] = ` +"" +`; diff --git a/packages/components/carousel/__tests__/carousel.spec.ts b/packages/components/carousel/__tests__/carousel.spec.ts new file mode 100644 index 000000000..8c717a489 --- /dev/null +++ b/packages/components/carousel/__tests__/carousel.spec.ts @@ -0,0 +1,116 @@ +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(resolve => setTimeout(() => resolve(), ms)) + const CarouselMount = (options?: MountingOptions>) => + mount(Carousel, { + slots: { + default: () => [h('div', 'card1'), h('div', 'card2'), h('div', 'card3')], + }, + ...(options as MountingOptions), + }) + + renderWork(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) + }) +}) diff --git a/packages/components/carousel/demo/Arrow.md b/packages/components/carousel/demo/Arrow.md new file mode 100644 index 000000000..a06e66a7a --- /dev/null +++ b/packages/components/carousel/demo/Arrow.md @@ -0,0 +1,9 @@ +--- +title: + zh: 箭头 +order: 3 +--- + +## zh + +支持自定义箭头展示 diff --git a/packages/components/carousel/demo/Arrow.vue b/packages/components/carousel/demo/Arrow.vue new file mode 100644 index 000000000..58fe22818 --- /dev/null +++ b/packages/components/carousel/demo/Arrow.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/components/carousel/demo/Autoplay.md b/packages/components/carousel/demo/Autoplay.md new file mode 100644 index 000000000..8f8ecafa6 --- /dev/null +++ b/packages/components/carousel/demo/Autoplay.md @@ -0,0 +1,9 @@ +--- +title: + zh: 自动轮播 +order: 2 +--- + +## zh + +定时切换下一张。 diff --git a/packages/components/carousel/demo/Autoplay.vue b/packages/components/carousel/demo/Autoplay.vue new file mode 100644 index 000000000..726c02eed --- /dev/null +++ b/packages/components/carousel/demo/Autoplay.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/components/carousel/demo/Basic.md b/packages/components/carousel/demo/Basic.md new file mode 100644 index 000000000..971592cbc --- /dev/null +++ b/packages/components/carousel/demo/Basic.md @@ -0,0 +1,10 @@ +--- +title: + zh: 基本使用 + en: Basic usage +order: 0 +--- + +## zh + +最简单的用法。 diff --git a/packages/components/carousel/demo/Basic.vue b/packages/components/carousel/demo/Basic.vue new file mode 100644 index 000000000..de19c9c9c --- /dev/null +++ b/packages/components/carousel/demo/Basic.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/components/carousel/demo/Dot.md b/packages/components/carousel/demo/Dot.md new file mode 100644 index 000000000..a0359df44 --- /dev/null +++ b/packages/components/carousel/demo/Dot.md @@ -0,0 +1,9 @@ +--- +title: + zh: 面板指示点 +order: 5 +--- + +## zh + +支持自定义面板指示点 diff --git a/packages/components/carousel/demo/Dot.vue b/packages/components/carousel/demo/Dot.vue new file mode 100644 index 000000000..008d7fe90 --- /dev/null +++ b/packages/components/carousel/demo/Dot.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/packages/components/carousel/demo/DotPlacement.md b/packages/components/carousel/demo/DotPlacement.md new file mode 100644 index 000000000..12831a870 --- /dev/null +++ b/packages/components/carousel/demo/DotPlacement.md @@ -0,0 +1,9 @@ +--- +title: + zh: 面板指示点 +order: 1 +--- + +## zh + +面板指示点的位置有4个方向。 diff --git a/packages/components/carousel/demo/DotPlacement.vue b/packages/components/carousel/demo/DotPlacement.vue new file mode 100644 index 000000000..c12f2c8ef --- /dev/null +++ b/packages/components/carousel/demo/DotPlacement.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/packages/components/carousel/demo/Trigger.md b/packages/components/carousel/demo/Trigger.md new file mode 100644 index 000000000..49edf50ee --- /dev/null +++ b/packages/components/carousel/demo/Trigger.md @@ -0,0 +1,9 @@ +--- +title: + zh: 指示点触发方式 +order: 4 +--- + +## zh + +支持设置鼠标经过指示点时触发切换 diff --git a/packages/components/carousel/demo/Trigger.vue b/packages/components/carousel/demo/Trigger.vue new file mode 100644 index 000000000..bdcdfcc77 --- /dev/null +++ b/packages/components/carousel/demo/Trigger.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/components/carousel/docs/Design.zh.md b/packages/components/carousel/docs/Design.zh.md new file mode 100644 index 000000000..eb5cc673f --- /dev/null +++ b/packages/components/carousel/docs/Design.zh.md @@ -0,0 +1,12 @@ +旋转木马,通常用来循环播放同一类型的交互内容。 + +### 什么情况下使用? + +- 当有一组平级的内容。 +- 当内容空间不足时,可以用走马灯的形式进行收纳,进行轮播展现。 +- 常用于一组图片或卡片轮播。 + +### 什么情况下不使用? + +- 展示一组图片或卡片,空间足够的情况下不需要用轮播图。 +- 图片或卡片的内容相互有逻辑关系,不适合用轮播图。 diff --git a/packages/components/carousel/docs/Index.zh.md b/packages/components/carousel/docs/Index.zh.md new file mode 100644 index 000000000..579942bbf --- /dev/null +++ b/packages/components/carousel/docs/Index.zh.md @@ -0,0 +1,37 @@ +--- +category: components +type: 数据展示 +title: Carousel +subtitle: 轮播图 +order: 0 +--- + +## API + +### IxCarousel + +#### CarouselProps + +| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | +| --- | --- | --- | --- | --- | --- | +| `autoplayTime` | 控制自动轮播的时间间隔 | `number` | `0` | ✅ | 值为`0`时不开启自动轮播 | +| `dotPlacement` | 面板指示点的位置 | `'top' \| 'start' \| 'bottom' \| 'end' \| 'none'` | `'bottom'` | ✅ | 为`'none'`时不显示面板指示点 | +| `showArrow` | 是否显示`prev`、`next`按钮 | `boolean` | `false` | ✅ | - | +| `trigger` | 面板指示点的触发方式 | `'click' \| 'hover'` | `'click'` | ✅ | - | +| `onChange` | 面板切换时会触发的回调函数 | `(prevIndex: number, nextIndex: number) => void` | - | - | - | + +#### CarouselSlots + +| 名称 | 说明 | 参数类型 | 备注 | +| --- | --- | --- | --- | +| `default` | 面板的内容 | - | - | +| `dot` | 面板指示点 | `{ index: number, isActive: boolean }` | `isActive`表示当前索引是否激活 | +| `arrow` | 自定义切换按钮 | `type: 'prev' \| 'next'` | - | + +#### CarseouselMethods + +| 名称 | 说明 | 参数类型 | 备注 | +| --- | --- | --- | --- | +| `goTo(slideIndex: number)` | 切换到指定面板 | `(slideIndex: number) => void` | - | +| `next()` | 切换到下一面板 | - | - | +| `prev()` | 切换到上一面板 | - | - | diff --git a/packages/components/carousel/index.ts b/packages/components/carousel/index.ts new file mode 100644 index 000000000..f8c0c7687 --- /dev/null +++ b/packages/components/carousel/index.ts @@ -0,0 +1,16 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { CarouselComponent } from './src/types' + +import Carousel from './src/Carousel' + +const IxCarousel = Carousel as unknown as CarouselComponent + +export { IxCarousel } + +export type { CarouselInstance, CarouselPublicProps as CarouselProps, DotPlacement, DotTrigger } from './src/types' diff --git a/packages/components/carousel/src/Carousel.tsx b/packages/components/carousel/src/Carousel.tsx new file mode 100644 index 000000000..b52ecef2b --- /dev/null +++ b/packages/components/carousel/src/Carousel.tsx @@ -0,0 +1,148 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { cloneVNode, computed, defineComponent, normalizeClass, ref } from 'vue' + +import { flattenNode } from '@idux/cdk/utils' +import { useGlobalConfig } from '@idux/components/config' +import { IxIcon } from '@idux/components/icon' + +import { useAutoplay } from './composables/useAutoplay' +import { useWalk } from './composables/useWalk' +import { carouselProps } from './types' + +export default defineComponent({ + name: 'IxCarousel', + props: carouselProps, + setup(props, { slots, expose }) { + const carouselRef = ref(null) + const common = useGlobalConfig('common') + const mergedPrefixCls = computed(() => `${common.prefixCls}-carousel`) + const config = useGlobalConfig('carousel') + const autoplayTime = computed(() => props.autoplayTime ?? config.autoplayTime) + const dotPlacement = computed(() => props.dotPlacement ?? config.dotPlacement) + const showArrow = computed(() => props.showArrow ?? config.showArrow) + const trigger = computed(() => props.trigger ?? config.trigger) + const children = computed(() => flattenNode(slots.default?.())) + const length = computed(() => children.value.length) + const vertical = computed(() => dotPlacement.value === 'start' || dotPlacement.value === 'end') + const itemClass = computed(() => `${mergedPrefixCls.value}-slide-item`) + const size = computed(() => { + const carousel = carouselRef.value + return { + width: carousel?.offsetWidth ?? 0, + height: carousel?.querySelector(`.${itemClass.value}`)?.offsetHeight ?? 0, + } + }) + const total = computed(() => length.value + 2) + + const slidesStyle = computed(() => { + const index = activeIndex.value % total.value + const offset = vertical.value + ? { top: `-${size.value.height * index}px` } + : { left: `-${size.value.width * index}px` } + + return { + width: `${total.value * size.value.width}px`, + ...offset, + } + }) + const slideItemStyle = computed(() => { + return { + width: `${size.value.width}px`, + height: '100%', + } + }) + + const classes = computed(() => { + const prefixCls = mergedPrefixCls.value + return normalizeClass({ + [prefixCls]: true, + [`${prefixCls}-vertical`]: vertical.value, + [`${prefixCls}-horizontal`]: !vertical.value, + }) + }) + const slidesClass = computed(() => { + const prefixCls = mergedPrefixCls.value + return normalizeClass({ + [`${prefixCls}-slides`]: true, + }) + }) + const dotClass = computed(() => { + const prefixCls = mergedPrefixCls.value + return normalizeClass({ + [`${prefixCls}-dot`]: true, + [`${prefixCls}-dot-${dotPlacement.value}`]: true, + }) + }) + + const { goTo, next, prev, onTransitionend, activeIndex } = useWalk(length, props) + expose({ goTo, next, prev }) + + useAutoplay(autoplayTime, next) + + const onClick = (slideIndex: number) => { + if (trigger.value === 'click') { + goTo(slideIndex) + } + } + const onMouseenter = (slideIndex: number) => { + if (trigger.value === 'hover') { + goTo(slideIndex) + } + } + + return () => { + const prefixCls = mergedPrefixCls.value + const startVNode = cloneVNode(children.value[length.value - 1]) + const endVNode = cloneVNode(children.value[0]) + const slides = [startVNode, ...children.value, endVNode].map(slideItem => ( +
+ {slideItem} +
+ )) + const prevArrow = slots.arrow?.({ type: 'prev' }) ?? + const nextArrow = slots.arrow?.({ type: 'next' }) ?? + const dots = Array.from({ length: length.value }).map((_, index: number) => { + const isActive = index + 1 === activeIndex.value + const itemClass = { + [`${prefixCls}-dot-item`]: true, + [`${prefixCls}-dot-item-active`]: isActive, + } + const children = slots.dot ? ( + slots.dot({ index, isActive }) + ) : ( + + ) + return ( +
  • onClick(index)} onMouseenter={() => onMouseenter(index)}> + {children} +
  • + ) + }) + + return ( +
    +
    + {slides} +
    + {showArrow.value && ( + <> +
    + {prevArrow} +
    +
    + {nextArrow} +
    + + )} + {dotPlacement.value !== 'none' &&
      {dots}
    } +
    + ) + } + }, +}) diff --git a/packages/components/carousel/src/composables/useAutoplay.ts b/packages/components/carousel/src/composables/useAutoplay.ts new file mode 100644 index 000000000..7a27f9b6d --- /dev/null +++ b/packages/components/carousel/src/composables/useAutoplay.ts @@ -0,0 +1,31 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { ComputedRef, onBeforeUnmount, watch } from 'vue' + +export const useAutoplay = (autoplayTime: ComputedRef, next: () => void): void => { + let timer: number | null = null + + watch( + autoplayTime, + (newVal: number) => { + timer && window.clearInterval(timer) + if (newVal) { + timer = window.setInterval(() => { + next() + }, newVal) + } + }, + { immediate: true }, + ) + + onBeforeUnmount(() => { + if (timer !== null) { + window.clearInterval(timer) + } + }) +} diff --git a/packages/components/carousel/src/composables/useWalk.ts b/packages/components/carousel/src/composables/useWalk.ts new file mode 100644 index 000000000..721bc12f9 --- /dev/null +++ b/packages/components/carousel/src/composables/useWalk.ts @@ -0,0 +1,83 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { CarouselProps } from '../types' + +import { ComputedRef, nextTick, watch } from 'vue' + +import { callEmit, useState } from '@idux/cdk/utils' + +interface WalkContext { + goTo(slideIndex: number): void + next(): void + prev(): void + onTransitionend(e: TransitionEvent): void + activeIndex: ComputedRef +} + +export const useWalk = (length: ComputedRef, props: CarouselProps): WalkContext => { + const [activeIndex, setActiveIndex] = useState(1) + let running = false + + watch(activeIndex, (newVal: number, oldVal: number) => { + if (newVal >= 1 && newVal <= length.value) { + callEmit(props.onChange, oldVal, newVal) + } + }) + + const goTo = (slideIndex: number) => { + running = true + if (activeIndex.value === 1 && slideIndex === length.value - 1) { + setActiveIndex(0) + } else if (activeIndex.value === length.value && slideIndex === 0) { + setActiveIndex(length.value + 1) + } else { + setActiveIndex(slideIndex + 1) + } + } + + const next = () => { + if (length.value <= 1 || running) { + return + } + running = true + setActiveIndex(activeIndex.value + 1) + } + const prev = () => { + if (length.value <= 1 || running) { + return + } + running = true + setActiveIndex(activeIndex.value - 1) + } + + const onTransitionend = (e: TransitionEvent) => { + running = false + if (activeIndex.value > 0 && activeIndex.value <= length.value) { + return + } + if (activeIndex.value === 0) { + setActiveIndex(length.value) + } else if (activeIndex.value === length.value + 1) { + setActiveIndex(1) + } + nextTick(() => { + const target = e.target as HTMLElement + target.style.transition = 'none' + void target.clientWidth + target.style.transition = '' + }) + } + + return { + goTo, + next, + prev, + onTransitionend, + activeIndex, + } +} diff --git a/packages/components/carousel/src/types.ts b/packages/components/carousel/src/types.ts new file mode 100644 index 000000000..2b46a2b54 --- /dev/null +++ b/packages/components/carousel/src/types.ts @@ -0,0 +1,39 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { IxInnerPropTypes, IxPublicPropTypes } from '@idux/cdk/utils' +import type { DefineComponent, HTMLAttributes } from 'vue' + +import { IxPropTypes } from '@idux/cdk/utils' + +export type DotPlacement = 'top' | 'start' | 'bottom' | 'end' | 'none' +const dotPlacement: DotPlacement[] = ['top', 'start', 'bottom', 'end', 'none'] + +export type DotTrigger = 'click' | 'hover' +const dotTrigger: DotTrigger[] = ['click', 'hover'] + +export const carouselProps = { + autoplayTime: IxPropTypes.number, + dotPlacement: IxPropTypes.oneOf(dotPlacement), + showArrow: IxPropTypes.bool, + trigger: IxPropTypes.oneOf(dotTrigger), + onChange: IxPropTypes.emit<(prevIndex: number, nextIndex: number) => void>(), +} + +export interface CarouselBindings { + next: () => void + prev: () => void + goTo: (slideIndex: number) => void +} + +export type CarouselProps = IxInnerPropTypes +export type CarouselPublicProps = IxPublicPropTypes +export type CarouselComponent = DefineComponent< + Omit & CarouselPublicProps, + CarouselBindings +> +export type CarouselInstance = InstanceType> diff --git a/packages/components/carousel/style/index.less b/packages/components/carousel/style/index.less new file mode 100644 index 000000000..52783a158 --- /dev/null +++ b/packages/components/carousel/style/index.less @@ -0,0 +1,94 @@ +@import '../../style/mixins/reset.less'; +@import './mixin.less'; + +.@{carousel-prefix} { + .reset-component(); + + width: 100%; + overflow: hidden; + position: relative; + + &-slides { + transition: all 0.3s; + position: relative; + } + + &-horizontal &-slide-item { + float: left; + } + + &-arrow { + font-size: @carousel-arrow-size; + color: @carousel-arrow-color; + opacity: @carousel-icon-opacity; + cursor: pointer; + + .carousel-vertical-center(); + + &:hover { + opacity: @carousel-icon-active-opacity; + } + + &-prev { + left: @carousel-arrow-spacing; + } + + &-next { + right: @carousel-arrow-spacing; + } + } + + &-horizontal &-dot { + .carousel-horizontal-center(); + } + + &-vertical &-dot { + flex-direction: column; + .carousel-vertical-center(); + } + + &-dot { + display: flex; + + &-item { + opacity: @carousel-icon-opacity; + cursor: pointer; + + &:hover, + &-active { + opacity: @carousel-icon-active-opacity; + } + + &-default { + width: @carousel-dot-size; + height: @carousel-dot-size; + border-radius: 50%; + background-color: @carousel-dot-background-color; + } + } + + .@{carousel-prefix}-vertical &-item + &-item { + margin-top: @carousel-dot-gap; + } + + .@{carousel-prefix}-horizontal &-item + &-item { + margin-left: @carousel-dot-gap; + } + + &-start { + left: @carousel-dot-spacing; + } + + &-end { + right: @carousel-dot-spacing; + } + + &-top { + top: @carousel-dot-spacing; + } + + &-bottom { + bottom: @carousel-dot-spacing; + } + } +} diff --git a/packages/components/carousel/style/mixin.less b/packages/components/carousel/style/mixin.less new file mode 100644 index 000000000..75ed122bc --- /dev/null +++ b/packages/components/carousel/style/mixin.less @@ -0,0 +1,11 @@ +.carousel-vertical-center () { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.carousel-horizontal-center () { + position: absolute; + left: 50%; + transform: translateX(-50%); +} diff --git a/packages/components/carousel/style/themes/default.less b/packages/components/carousel/style/themes/default.less new file mode 100644 index 000000000..236e4c351 --- /dev/null +++ b/packages/components/carousel/style/themes/default.less @@ -0,0 +1,14 @@ +@import '../../../style/themes/default.less'; +@import '../index.less'; + +@carousel-arrow-size: @font-size-2xl; +@carousel-arrow-color: @color-white; +@carousel-arrow-spacing: @spacing-gutter; + +@carousel-dot-size: @spacing-gutter; +@carousel-dot-background-color: @color-white; +@carousel-dot-gap: @spacing-md; +@carousel-dot-spacing: @spacing-lg; + +@carousel-icon-opacity: 0.3; +@carousel-icon-active-opacity: 0.8; diff --git a/packages/components/carousel/style/themes/default.ts b/packages/components/carousel/style/themes/default.ts new file mode 100644 index 000000000..8aaddc579 --- /dev/null +++ b/packages/components/carousel/style/themes/default.ts @@ -0,0 +1,5 @@ +// style dependencies +import '@idux/components/style/core/default' +import '@idux/components/icon/style/themes/default' + +import './default.less' diff --git a/packages/components/config/src/defaultConfig.ts b/packages/components/config/src/defaultConfig.ts index f4af90674..4b1300db2 100644 --- a/packages/components/config/src/defaultConfig.ts +++ b/packages/components/config/src/defaultConfig.ts @@ -12,6 +12,7 @@ import type { BackTopConfig, BadgeConfig, CardConfig, + CarouselConfig, CheckboxConfig, CollapseConfig, CommonConfig, @@ -311,6 +312,13 @@ const skeleton: SkeletonConfig = { animated: true, } +const carousel: CarouselConfig = { + autoplayTime: 0, + dotPlacement: 'bottom', + showArrow: false, + trigger: 'click', +} + const drawer: DrawerConfig = { closable: true, closeOnEsc: true, @@ -390,6 +398,7 @@ export const defaultConfig: GlobalConfig = { badge, card, empty, + carousel, list, collapse, image, diff --git a/packages/components/config/src/types.ts b/packages/components/config/src/types.ts index 020dd886a..4658ee65c 100644 --- a/packages/components/config/src/types.ts +++ b/packages/components/config/src/types.ts @@ -11,6 +11,7 @@ import type { PortalTargetType } from '@idux/cdk/portal' import type { AlertType } from '@idux/components/alert' import type { AvatarShape, AvatarSize } from '@idux/components/avatar' import type { CardSize } from '@idux/components/card' +import type { DotPlacement, DotTrigger } from '@idux/components/carousel' import type { DatePickerType } from '@idux/components/date-picker/src/types' import type { DividerPosition, DividerType } from '@idux/components/divider' import type { FormLabelAlign, FormLayout, FormSize } from '@idux/components/form' @@ -327,6 +328,13 @@ export interface SkeletonConfig { animated: boolean } +export interface CarouselConfig { + autoplayTime: number + dotPlacement: DotPlacement + showArrow: boolean + trigger: DotTrigger +} + export interface DrawerConfig { closable: boolean closeIcon: string @@ -414,6 +422,7 @@ export interface GlobalConfig { badge: BadgeConfig card: CardConfig empty: EmptyConfig + carousel: CarouselConfig list: ListConfig collapse: CollapseConfig image: ImageConfig diff --git a/packages/components/default.less b/packages/components/default.less index f0aa28b2d..446caadcf 100644 --- a/packages/components/default.less +++ b/packages/components/default.less @@ -13,6 +13,7 @@ @import './badge/style/themes/default.less'; @import './button/style/themes/default.less'; @import './card/style/themes/default.less'; +@import './carousel/style//themes/default.less'; @import './checkbox/style/themes/default.less'; @import './collapse/style/themes/default.less'; @import './date-picker/style/themes/default.less'; diff --git a/packages/components/index.ts b/packages/components/index.ts index a59524386..40a79b1ee 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -15,6 +15,7 @@ import { IxBackTop } from '@idux/components/back-top' import { IxBadge } from '@idux/components/badge' import { IxButton, IxButtonGroup } from '@idux/components/button' import { IxCard, IxCardGrid } from '@idux/components/card' +import { IxCarousel } from '@idux/components/carousel' import { IxCheckbox, IxCheckboxGroup } from '@idux/components/checkbox' import { IxCollapse, IxCollapsePanel } from '@idux/components/collapse' import { IxDatePicker } from '@idux/components/date-picker' @@ -73,6 +74,7 @@ const components = [ IxButtonGroup, IxCard, IxCardGrid, + IxCarousel, IxCheckbox, IxCheckboxGroup, IxCollapse, diff --git a/packages/components/style/variable/prefix.less b/packages/components/style/variable/prefix.less index 7a4797306..96335c805 100644 --- a/packages/components/style/variable/prefix.less +++ b/packages/components/style/variable/prefix.less @@ -27,6 +27,7 @@ @card-prefix: ~'@{idux-prefix}-card'; @list-prefix: ~'@{idux-prefix}-list'; @list-item-prefix: ~'@{idux-prefix}-list-item'; +@carousel-prefix: ~'@{idux-prefix}-carousel'; @collapse-prefix: ~'@{idux-prefix}-collapse'; @collapse-panel-prefix: ~'@{idux-prefix}-collapse-panel'; @empty-prefix: ~'@{idux-prefix}-empty';