diff --git a/components/carousel/demo/autoplay.vue b/components/carousel/demo/autoplay.vue index e0a34fbbb..cb6ef6656 100644 --- a/components/carousel/demo/autoplay.vue +++ b/components/carousel/demo/autoplay.vue @@ -26,15 +26,14 @@ Timing of scrolling to the next card/picture. diff --git a/components/carousel/demo/basic.vue b/components/carousel/demo/basic.vue index 78dbddb04..b370df0c2 100644 --- a/components/carousel/demo/basic.vue +++ b/components/carousel/demo/basic.vue @@ -41,7 +41,7 @@ export default defineComponent({ diff --git a/components/carousel/demo/customArrows.vue b/components/carousel/demo/customArrows.vue index 7e224cbaa..e8d55e3da 100644 --- a/components/carousel/demo/customArrows.vue +++ b/components/carousel/demo/customArrows.vue @@ -46,7 +46,7 @@ export default defineComponent({ diff --git a/components/carousel/demo/customPaging.vue b/components/carousel/demo/customPaging.vue index 7b9b4ebdd..f6a6f69ea 100644 --- a/components/carousel/demo/customPaging.vue +++ b/components/carousel/demo/customPaging.vue @@ -46,33 +46,33 @@ export default defineComponent({ diff --git a/components/carousel/demo/fade.vue b/components/carousel/demo/fade.vue index bd5fce111..1b0c16157 100644 --- a/components/carousel/demo/fade.vue +++ b/components/carousel/demo/fade.vue @@ -26,7 +26,7 @@ Slides use fade for transition. diff --git a/components/carousel/demo/position.vue b/components/carousel/demo/position.vue index 215565fa7..d21f0e680 100644 --- a/components/carousel/demo/position.vue +++ b/components/carousel/demo/position.vue @@ -43,7 +43,7 @@ export default defineComponent({ diff --git a/components/carousel/index.en-US.md b/components/carousel/index.en-US.md index b0d3c8380..be56a8ef8 100644 --- a/components/carousel/index.en-US.md +++ b/components/carousel/index.en-US.md @@ -2,7 +2,7 @@ category: Components type: Data Display title: Carousel -cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original --- A carousel component. Scales with its container. diff --git a/components/carousel/index.tsx b/components/carousel/index.tsx index 77d96cc9d..6166c3c25 100644 --- a/components/carousel/index.tsx +++ b/components/carousel/index.tsx @@ -1,12 +1,15 @@ -import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'; +import type { CSSProperties, ExtractPropTypes } from 'vue'; import { ref, computed, watchEffect, defineComponent } from 'vue'; import PropTypes from '../_util/vue-types'; import warning from '../_util/warning'; import classNames from '../_util/classNames'; import SlickCarousel from '../vc-slick'; -import { withInstall } from '../_util/type'; +import { withInstall, booleanType, functionType, stringType } from '../_util/type'; import useConfigInject from '../config-provider/hooks/useConfigInject'; +// CSSINJS +import useStyle from './style'; + export type SwipeDirection = 'left' | 'down' | 'right' | 'up' | string; export type LazyLoadTypes = 'ondemand' | 'progressive'; @@ -24,49 +27,49 @@ export interface CarouselRef { // Carousel export const carouselProps = () => ({ - effect: String as PropType, - dots: { type: Boolean, default: true }, - vertical: { type: Boolean, default: undefined }, - autoplay: { type: Boolean, default: undefined }, + effect: stringType(), + dots: booleanType(true), + vertical: booleanType(), + autoplay: booleanType(), easing: String, - beforeChange: Function as PropType<(currentSlide: number, nextSlide: number) => void>, - afterChange: Function as PropType<(currentSlide: number) => void>, + beforeChange: functionType<(currentSlide: number, nextSlide: number) => void>(), + afterChange: functionType<(currentSlide: number) => void>(), // style: PropTypes.React.CSSProperties, prefixCls: String, - accessibility: { type: Boolean, default: undefined }, + accessibility: booleanType(), nextArrow: PropTypes.any, prevArrow: PropTypes.any, - pauseOnHover: { type: Boolean, default: undefined }, + pauseOnHover: booleanType(), // className: String, - adaptiveHeight: { type: Boolean, default: undefined }, - arrows: { type: Boolean, default: false }, + adaptiveHeight: booleanType(), + arrows: booleanType(false), autoplaySpeed: Number, - centerMode: { type: Boolean, default: undefined }, + centerMode: booleanType(), centerPadding: String, cssEase: String, dotsClass: String, - draggable: { type: Boolean, default: false }, - fade: { type: Boolean, default: undefined }, - focusOnSelect: { type: Boolean, default: undefined }, - infinite: { type: Boolean, default: undefined }, + draggable: booleanType(false), + fade: booleanType(), + focusOnSelect: booleanType(), + infinite: booleanType(), initialSlide: Number, - lazyLoad: String as PropType, - rtl: { type: Boolean, default: undefined }, + lazyLoad: stringType(), + rtl: booleanType(), slide: String, slidesToShow: Number, slidesToScroll: Number, speed: Number, - swipe: { type: Boolean, default: undefined }, - swipeToSlide: { type: Boolean, default: undefined }, - swipeEvent: Function as PropType<(swipeDirection: SwipeDirection) => void>, - touchMove: { type: Boolean, default: undefined }, + swipe: booleanType(), + swipeToSlide: booleanType(), + swipeEvent: functionType<(swipeDirection: SwipeDirection) => void>(), + touchMove: booleanType(), touchThreshold: Number, - variableWidth: { type: Boolean, default: undefined }, - useCSS: { type: Boolean, default: undefined }, + variableWidth: booleanType(), + useCSS: booleanType(), slickGoTo: Number, responsive: Array, - dotPosition: { type: String as PropType, default: undefined }, - verticalSwiping: { type: Boolean, default: false }, + dotPosition: stringType(), + verticalSwiping: booleanType(false), }); export type CarouselProps = Partial>>; const Carousel = defineComponent({ @@ -104,6 +107,10 @@ const Carousel = defineComponent({ ); }); const { prefixCls, direction } = useConfigInject('carousel', props); + + // style + const [wrapSSR, hashId] = useStyle(prefixCls); + const dotPosition = computed(() => { if (props.dotPosition) return props.dotPosition; if (props.vertical !== undefined) return props.vertical ? 'right' : 'bottom'; @@ -122,12 +129,16 @@ const Carousel = defineComponent({ const { dots, arrows, draggable, effect } = props; const { class: cls, style, ...restAttrs } = attrs; const fade = effect === 'fade' ? true : props.fade; - const className = classNames(prefixCls.value, { - [`${prefixCls.value}-rtl`]: direction.value === 'rtl', - [`${prefixCls.value}-vertical`]: vertical.value, - [`${cls}`]: !!cls, - }); - return ( + const className = classNames( + prefixCls.value, + { + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${prefixCls.value}-vertical`]: vertical.value, + [`${cls}`]: !!cls, + }, + hashId.value, + ); + return wrapSSR(
-
+ , ); }; }, diff --git a/components/carousel/index.zh-CN.md b/components/carousel/index.zh-CN.md index bb02f2be8..248be3b45 100644 --- a/components/carousel/index.zh-CN.md +++ b/components/carousel/index.zh-CN.md @@ -3,7 +3,7 @@ category: Components type: 数据展示 title: Carousel subtitle: 走马灯 -cover: https://gw.alipayobjects.com/zos/antfincdn/%24C9tmj978R/Carousel.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*bPMSSqbaTMkAAAAAAAAAAAAADrJ8AQ/original --- 旋转木马,一组轮播的区域。 diff --git a/components/carousel/style/index.less b/components/carousel/style/index.less deleted file mode 100644 index 031383b2b..000000000 --- a/components/carousel/style/index.less +++ /dev/null @@ -1,294 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; - -.@{carousel-prefix-cls} { - .reset-component(); - - .slick-slider { - position: relative; - display: block; - box-sizing: border-box; - touch-action: pan-y; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; - } - - .slick-list { - position: relative; - display: block; - margin: 0; - padding: 0; - overflow: hidden; - - &:focus { - outline: none; - } - - &.dragging { - cursor: pointer; - } - - .slick-slide { - pointer-events: none; - - // https://github.com/ant-design/ant-design/issues/23294 - input.@{ant-prefix}-radio-input, - input.@{ant-prefix}-checkbox-input { - visibility: hidden; - } - - &.slick-active { - pointer-events: auto; - - input.@{ant-prefix}-radio-input, - input.@{ant-prefix}-checkbox-input { - visibility: visible; - } - } - - // fix Carousel content height not match parent node - // when children is empty node - // https://github.com/ant-design/ant-design/issues/25878 - > div > div { - vertical-align: bottom; - } - } - } - - .slick-slider .slick-track, - .slick-slider .slick-list { - transform: translate3d(0, 0, 0); - touch-action: pan-y; - } - - .slick-track { - position: relative; - top: 0; - left: 0; - display: block; - - &::before, - &::after { - display: table; - content: ''; - } - - &::after { - clear: both; - } - - .slick-loading & { - visibility: hidden; - } - } - - .slick-slide { - display: none; - float: left; - height: 100%; - min-height: 1px; - - img { - display: block; - } - - &.slick-loading img { - display: none; - } - - &.dragging img { - pointer-events: none; - } - } - - .slick-initialized .slick-slide { - display: block; - } - - .slick-loading .slick-slide { - visibility: hidden; - } - - .slick-vertical .slick-slide { - display: block; - height: auto; - } - - .slick-arrow.slick-hidden { - display: none; - } - - // Arrows - .slick-prev, - .slick-next { - position: absolute; - top: 50%; - display: block; - width: 20px; - height: 20px; - margin-top: -10px; - padding: 0; - color: transparent; - font-size: 0; - line-height: 0; - background: transparent; - border: 0; - outline: none; - cursor: pointer; - - &:hover, - &:focus { - color: transparent; - background: transparent; - outline: none; - - &::before { - opacity: 1; - } - } - - &.slick-disabled::before { - opacity: 0.25; - } - } - - .slick-prev { - left: -25px; - - &::before { - content: '←'; - } - } - - .slick-next { - right: -25px; - - &::before { - content: '→'; - } - } - - // Dots - .slick-dots { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 15; - display: flex !important; - justify-content: center; - margin-right: 15%; - margin-left: 15%; - padding-left: 0; - list-style: none; - - &-bottom { - bottom: 12px; - } - - &-top { - top: 12px; - bottom: auto; - } - - li { - position: relative; - display: inline-block; - flex: 0 1 auto; - box-sizing: content-box; - width: @carousel-dot-width; - height: @carousel-dot-height; - margin: 0 2px; - margin-right: 3px; - margin-left: 3px; - padding: 0; - text-align: center; - text-indent: -999px; - vertical-align: top; - transition: all 0.5s; - - button { - display: block; - width: 100%; - height: @carousel-dot-height; - padding: 0; - color: transparent; - font-size: 0; - background: @component-background; - border: 0; - border-radius: 1px; - outline: none; - cursor: pointer; - opacity: 0.3; - transition: all 0.5s; - - &:hover, - &:focus { - opacity: 0.75; - } - } - - &.slick-active { - width: @carousel-dot-active-width; - - & button { - background: @component-background; - opacity: 1; - } - - &:hover, - &:focus { - opacity: 1; - } - } - } - } -} - -.@{ant-prefix}-carousel-vertical { - .slick-dots { - top: 50%; - bottom: auto; - flex-direction: column; - width: @carousel-dot-height; - height: auto; - margin: 0; - transform: translateY(-50%); - - &-left { - right: auto; - left: 12px; - } - - &-right { - right: 12px; - left: auto; - } - - li { - width: @carousel-dot-height; - height: @carousel-dot-width; - margin: 4px 2px; - vertical-align: baseline; - - button { - width: @carousel-dot-height; - height: @carousel-dot-width; - } - - &.slick-active { - width: @carousel-dot-height; - height: @carousel-dot-active-width; - - button { - width: @carousel-dot-height; - height: @carousel-dot-active-width; - } - } - } - } -} - -@import './rtl'; diff --git a/components/carousel/style/index.tsx b/components/carousel/style/index.tsx index 3a3ab0de5..41d453c6d 100644 --- a/components/carousel/style/index.tsx +++ b/components/carousel/style/index.tsx @@ -1,2 +1,350 @@ -import '../../style/index.less'; -import './index.less'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent } from '../../_style'; + +export interface ComponentToken { + dotWidth: number; + dotHeight: number; + dotWidthActive: number; +} + +interface CarouselToken extends FullToken<'Carousel'> { + carouselArrowSize: number; + carouselDotOffset: number; + carouselDotInline: number; +} + +const genCarouselStyle: GenerateStyle = token => { + const { componentCls, antCls, carouselArrowSize, carouselDotOffset, marginXXS } = token; + const arrowOffset = -carouselArrowSize * 1.25; + + const carouselDotMargin = marginXXS; + + return { + [componentCls]: { + ...resetComponent(token), + + '.slick-slider': { + position: 'relative', + display: 'block', + boxSizing: 'border-box', + touchAction: 'pan-y', + WebkitTouchCallout: 'none', + WebkitTapHighlightColor: 'transparent', + + '.slick-track, .slick-list': { + transform: 'translate3d(0, 0, 0)', + touchAction: 'pan-y', + }, + }, + + '.slick-list': { + position: 'relative', + display: 'block', + margin: 0, + padding: 0, + overflow: 'hidden', + + '&:focus': { + outline: 'none', + }, + + '&.dragging': { + cursor: 'pointer', + }, + + '.slick-slide': { + pointerEvents: 'none', + + // https://github.com/ant-design/ant-design/issues/23294 + [`input${antCls}-radio-input, input${antCls}-checkbox-input`]: { + visibility: 'hidden', + }, + + '&.slick-active': { + pointerEvents: 'auto', + + [`input${antCls}-radio-input, input${antCls}-checkbox-input`]: { + visibility: 'visible', + }, + }, + + // fix Carousel content height not match parent node + // when children is empty node + // https://github.com/ant-design/ant-design/issues/25878 + '> div > div': { + verticalAlign: 'bottom', + }, + }, + }, + + '.slick-track': { + position: 'relative', + top: 0, + insetInlineStart: 0, + display: 'block', + + '&::before, &::after': { + display: 'table', + content: '""', + }, + + '&::after': { + clear: 'both', + }, + }, + + '.slick-slide': { + display: 'none', + float: 'left', + height: '100%', + minHeight: 1, + + img: { + display: 'block', + }, + + '&.dragging img': { + pointerEvents: 'none', + }, + }, + + '.slick-initialized .slick-slide': { + display: 'block', + }, + + '.slick-vertical .slick-slide': { + display: 'block', + height: 'auto', + }, + + '.slick-arrow.slick-hidden': { + display: 'none', + }, + + // Arrows + '.slick-prev, .slick-next': { + position: 'absolute', + top: '50%', + display: 'block', + width: carouselArrowSize, + height: carouselArrowSize, + marginTop: -carouselArrowSize / 2, + padding: 0, + color: 'transparent', + fontSize: 0, + lineHeight: 0, + background: 'transparent', + border: 0, + outline: 'none', + cursor: 'pointer', + + '&:hover, &:focus': { + color: 'transparent', + background: 'transparent', + outline: 'none', + + '&::before': { + opacity: 1, + }, + }, + + '&.slick-disabled::before': { + opacity: 0.25, + }, + }, + + '.slick-prev': { + insetInlineStart: arrowOffset, + + '&::before': { + content: '"←"', + }, + }, + + '.slick-next': { + insetInlineEnd: arrowOffset, + + '&::before': { + content: '"→"', + }, + }, + + // Dots + '.slick-dots': { + position: 'absolute', + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + zIndex: 15, + display: 'flex !important', + justifyContent: 'center', + paddingInlineStart: 0, + listStyle: 'none', + + '&-bottom': { + bottom: carouselDotOffset, + }, + + '&-top': { + top: carouselDotOffset, + bottom: 'auto', + }, + + li: { + position: 'relative', + display: 'inline-block', + flex: '0 1 auto', + boxSizing: 'content-box', + width: token.dotWidth, + height: token.dotHeight, + marginInline: carouselDotMargin, + padding: 0, + textAlign: 'center', + textIndent: -999, + verticalAlign: 'top', + transition: `all ${token.motionDurationSlow}`, + + button: { + position: 'relative', + display: 'block', + width: '100%', + height: token.dotHeight, + padding: 0, + color: 'transparent', + fontSize: 0, + background: token.colorBgContainer, + border: 0, + borderRadius: 1, + outline: 'none', + cursor: 'pointer', + opacity: 0.3, + transition: `all ${token.motionDurationSlow}`, + + '&: hover, &:focus': { + opacity: 0.75, + }, + + '&::after': { + position: 'absolute', + inset: -carouselDotMargin, + content: '""', + }, + }, + + '&.slick-active': { + width: token.dotWidthActive, + + '& button': { + background: token.colorBgContainer, + opacity: 1, + }, + + '&: hover, &:focus': { + opacity: 1, + }, + }, + }, + }, + }, + }; +}; + +const genCarouselVerticalStyle: GenerateStyle = token => { + const { componentCls, carouselDotOffset, marginXXS } = token; + + const reverseSizeOfDot = { + width: token.dotHeight, + height: token.dotWidth, + }; + + return { + [`${componentCls}-vertical`]: { + '.slick-dots': { + top: '50%', + bottom: 'auto', + flexDirection: 'column', + width: token.dotHeight, + height: 'auto', + margin: 0, + transform: 'translateY(-50%)', + + '&-left': { + insetInlineEnd: 'auto', + insetInlineStart: carouselDotOffset, + }, + + '&-right': { + insetInlineEnd: carouselDotOffset, + insetInlineStart: 'auto', + }, + + li: { + // reverse width and height in vertical situation + ...reverseSizeOfDot, + margin: `${marginXXS}px 0`, + verticalAlign: 'baseline', + + button: reverseSizeOfDot, + + '&.slick-active': { + ...reverseSizeOfDot, + + button: reverseSizeOfDot, + }, + }, + }, + }, + }; +}; + +const genCarouselRtlStyle: GenerateStyle = token => { + const { componentCls } = token; + + return [ + { + [`${componentCls}-rtl`]: { + direction: 'rtl', + + // Dots + '.slick-dots': { + [`${componentCls}-rtl&`]: { + flexDirection: 'row-reverse', + }, + }, + }, + }, + { + [`${componentCls}-vertical`]: { + '.slick-dots': { + [`${componentCls}-rtl&`]: { + flexDirection: 'column', + }, + }, + }, + }, + ]; +}; + +// ============================== Export ============================== +export default genComponentStyleHook( + 'Carousel', + token => { + const { controlHeightLG, controlHeightSM } = token; + const carouselToken = mergeToken(token, { + carouselArrowSize: controlHeightLG / 2, + carouselDotOffset: controlHeightSM / 2, + }); + + return [ + genCarouselStyle(carouselToken), + genCarouselVerticalStyle(carouselToken), + genCarouselRtlStyle(carouselToken), + ]; + }, + { + dotWidth: 16, + dotHeight: 3, + dotWidthActive: 24, + }, +); diff --git a/components/carousel/style/rtl.less b/components/carousel/style/rtl.less deleted file mode 100644 index c2853a2ac..000000000 --- a/components/carousel/style/rtl.less +++ /dev/null @@ -1,54 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@carousel-prefix-cls: ~'@{ant-prefix}-carousel'; - -.@{carousel-prefix-cls} { - &-rtl { - direction: rtl; - } - - .slick-track { - .@{carousel-prefix-cls}-rtl & { - right: 0; - left: auto; - } - } - - .slick-prev { - .@{carousel-prefix-cls}-rtl & { - right: -25px; - left: auto; - - &::before { - content: '→'; - } - } - } - - .slick-next { - .@{carousel-prefix-cls}-rtl & { - right: auto; - left: -25px; - - &::before { - content: '←'; - } - } - } - - // Dots - .slick-dots { - .@{carousel-prefix-cls}-rtl& { - flex-direction: row-reverse; - } - } -} - -.@{ant-prefix}-carousel-vertical { - .slick-dots { - .@{carousel-prefix-cls}-rtl& { - flex-direction: column; - } - } -} diff --git a/components/style.ts b/components/style.ts index 50dd6b1a0..c0246e372 100644 --- a/components/style.ts +++ b/components/style.ts @@ -19,7 +19,7 @@ import './mentions/style'; // import './divider/style'; // import './card/style'; import './collapse/style'; -import './carousel/style'; +// import './carousel/style'; // import './notification/style'; // import './message/style'; // import './spin/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 4063c7c9b..bd8d41d2b 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -6,7 +6,7 @@ import type { ComponentToken as ButtonComponentToken } from '../../button/style' // import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style'; // import type { ComponentToken as CalendarComponentToken } from '../../calendar/style'; import type { ComponentToken as CardComponentToken } from '../../card/style'; -// import type { ComponentToken as CarouselComponentToken } from '../../carousel/style'; +import type { ComponentToken as CarouselComponentToken } from '../../carousel/style'; // import type { ComponentToken as CascaderComponentToken } from '../../cascader/style'; // import type { ComponentToken as CheckboxComponentToken } from '../../checkbox/style'; // import type { ComponentToken as CollapseComponentToken } from '../../collapse/style'; @@ -60,7 +60,7 @@ export interface ComponentTokenMap { Button?: ButtonComponentToken; Breadcrumb?: {}; Card?: CardComponentToken; - // Carousel?: CarouselComponentToken; + Carousel?: CarouselComponentToken; // Cascader?: CascaderComponentToken; // Checkbox?: CheckboxComponentToken; // Collapse?: CollapseComponentToken;