diff --git a/components/slider/index.tsx b/components/slider/index.tsx index 687b15b30e..94da1e41ae 100644 --- a/components/slider/index.tsx +++ b/components/slider/index.tsx @@ -12,6 +12,9 @@ import classNames from '../_util/classNames'; import { useInjectFormItemContext } from '../form/FormItemContext'; import type { FocusEventHandler } from '../_util/EventInterface'; +// CSSINJS +import useStyle from './style'; + export type SliderValue = number | [number, number]; interface SliderMarks { @@ -87,6 +90,10 @@ const Slider = defineComponent({ setup(props, { attrs, slots, emit, expose }) { const { prefixCls, rootPrefixCls, direction, getPopupContainer, configProvider } = useConfigInject('slider', props); + + // style + const [wrapSSR, hashId] = useStyle(prefixCls); + const formItemContext = useInjectFormItemContext(); const sliderRef = ref(); const visibles = ref({}); @@ -156,9 +163,13 @@ const Slider = defineComponent({ ...restProps } = props; const tooltipPrefixCls = configProvider.getPrefixCls('tooltip', customizeTooltipPrefixCls); - const cls = classNames(attrs.class, { - [`${prefixCls.value}-rtl`]: direction.value === 'rtl', - }); + const cls = classNames( + attrs.class, + { + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + }, + hashId.value, + ); // make reverse default on rtl direction if (direction.value === 'rtl' && !restProps.vertical) { @@ -172,8 +183,9 @@ const Slider = defineComponent({ } if (range) { - return ( + return wrapSSR( + />, ); } - return ( + return wrapSSR( + />, ); }; }, diff --git a/components/slider/style/index.less b/components/slider/style/index.less deleted file mode 100644 index d5967bb08f..0000000000 --- a/components/slider/style/index.less +++ /dev/null @@ -1,212 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@slider-prefix-cls: ~'@{ant-prefix}-slider'; - -.@{slider-prefix-cls} { - .reset-component(); - - position: relative; - height: 12px; - margin: @slider-margin; - padding: 4px 0; - cursor: pointer; - touch-action: none; - - .vertical(); - - &-with-marks { - margin-bottom: 28px; - } - - &-rail { - position: absolute; - width: 100%; - height: 4px; - background-color: @slider-rail-background-color; - border-radius: @border-radius-base; - transition: background-color 0.3s; - } - - &-track { - position: absolute; - height: 4px; - background-color: @slider-track-background-color; - border-radius: @border-radius-base; - transition: background-color 0.3s; - } - - &-handle { - position: absolute; - width: @slider-handle-size; - height: @slider-handle-size; - margin-top: @slider-handle-margin-top; - background-color: @slider-handle-background-color; - border: solid @slider-handle-border-width @slider-handle-color; - border-radius: 50%; - box-shadow: @slider-handle-shadow; - cursor: pointer; - transition: border-color 0.3s, box-shadow 0.6s, - transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); - - &-dragging&-dragging&-dragging { - border-color: @slider-handle-color-focus; - box-shadow: 0 0 0 5px @slider-handle-color-focus-shadow; - } - - &:focus { - border-color: @slider-handle-color-focus; - outline: none; - box-shadow: 0 0 0 5px @slider-handle-color-focus-shadow; - } - - &.@{ant-prefix}-tooltip-open { - border-color: @slider-handle-color-tooltip-open; - } - } - - &:hover { - .@{slider-prefix-cls}-rail { - background-color: @slider-rail-background-color-hover; - } - .@{slider-prefix-cls}-track { - background-color: @slider-track-background-color-hover; - } - .@{slider-prefix-cls}-handle:not(.@{ant-prefix}-tooltip-open) { - border-color: @slider-handle-color-hover; - } - } - - &-mark { - position: absolute; - top: 14px; - left: 0; - width: 100%; - font-size: @font-size-base; - } - - &-mark-text { - position: absolute; - display: inline-block; - color: @text-color-secondary; - text-align: center; - word-break: keep-all; - cursor: pointer; - user-select: none; - - &-active { - color: @text-color; - } - } - - &-step { - position: absolute; - width: 100%; - height: 4px; - background: transparent; - } - - &-dot { - position: absolute; - top: -2px; - width: 8px; - height: 8px; - margin-left: -4px; - background-color: @component-background; - border: 2px solid @slider-dot-border-color; - border-radius: 50%; - cursor: pointer; - - &:first-child { - margin-left: -4px; - } - - &:last-child { - margin-left: -4px; - } - - &-active { - border-color: @slider-dot-border-color-active; - } - } - - &-disabled { - cursor: not-allowed; - - .@{slider-prefix-cls}-rail { - background-color: @slider-rail-background-color !important; - } - - .@{slider-prefix-cls}-track { - background-color: @slider-disabled-color !important; - } - - .@{slider-prefix-cls}-handle, - .@{slider-prefix-cls}-dot { - background-color: @component-background; - border-color: @slider-disabled-color !important; - box-shadow: none; - cursor: not-allowed; - } - - .@{slider-prefix-cls}-mark-text, - .@{slider-prefix-cls}-dot { - cursor: not-allowed !important; - } - } -} - -.vertical() { - &-vertical { - width: 12px; - height: 100%; - margin: 6px 10px; - padding: 0 4px; - - .@{slider-prefix-cls}-rail { - width: 4px; - height: 100%; - } - - .@{slider-prefix-cls}-track { - width: 4px; - } - - .@{slider-prefix-cls}-handle { - margin-top: -6px; // we chould consider border width as well: (10 + 2 ) / 2 - margin-left: -5px; - } - - .@{slider-prefix-cls}-mark { - top: 0; - left: 12px; - width: 18px; - height: 100%; - } - - .@{slider-prefix-cls}-mark-text { - left: 4px; - white-space: nowrap; - } - - .@{slider-prefix-cls}-step { - width: 4px; - height: 100%; - } - - .@{slider-prefix-cls}-dot { - top: auto; - left: 2px; - margin-bottom: -4px; - } - } - - &-tooltip { - // https://github.com/ant-design/ant-design/issues/20014 - .@{ant-prefix}-tooltip-inner { - min-width: unset; - } - } -} - -@import './rtl'; diff --git a/components/slider/style/index.tsx b/components/slider/style/index.tsx index 7bd39f61bf..70b381029f 100644 --- a/components/slider/style/index.tsx +++ b/components/slider/style/index.tsx @@ -1,5 +1,343 @@ -import '../../style/index.less'; -import './index.less'; +import type { CSSObject } from '../../_util/cssinjs'; +import type { CSSProperties } from 'vue'; +import { TinyColor } from '@ctrl/tinycolor'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent } from '../../_style'; -// style dependencies -import '../../tooltip/style'; +// Direction naming standard: +// Horizontal base: +// -0------------- +// vertical: part (水平时,垂直方向命名为 part) +// horizontal: full (水平时,水平方向命名为 full) + +export interface ComponentToken { + controlSize: number; + railSize: number; + handleSize: number; + handleSizeHover: number; + handleLineWidth: number; + handleLineWidthHover: number; + dotSize: number; +} + +interface SliderToken extends FullToken<'Slider'> { + marginFull: number; + marginPart: number; + marginPartWithMark: number; +} + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = token => { + const { componentCls, controlSize, dotSize, marginFull, marginPart, colorFillContentHover } = + token; + + return { + [componentCls]: { + ...resetComponent(token), + + position: 'relative', + height: controlSize, + margin: `${marginPart}px ${marginFull}px`, + padding: 0, + cursor: 'pointer', + touchAction: 'none', + + [`&-vertical`]: { + margin: `${marginFull}px ${marginPart}px`, + }, + + [`${componentCls}-rail`]: { + position: 'absolute', + backgroundColor: token.colorFillTertiary, + borderRadius: token.borderRadiusXS, + transition: `background-color ${token.motionDurationMid}`, + }, + + [`${componentCls}-track`]: { + position: 'absolute', + backgroundColor: token.colorPrimaryBorder, + borderRadius: token.borderRadiusXS, + transition: `background-color ${token.motionDurationMid}`, + }, + + '&:hover': { + [`${componentCls}-rail`]: { + backgroundColor: token.colorFillSecondary, + }, + + [`${componentCls}-track`]: { + backgroundColor: token.colorPrimaryBorderHover, + }, + + [`${componentCls}-dot`]: { + borderColor: colorFillContentHover, + }, + + [`${componentCls}-handle::after`]: { + boxShadow: `0 0 0 ${token.handleLineWidth}px ${token.colorPrimaryBorderHover}`, + }, + + [`${componentCls}-dot-active`]: { + borderColor: token.colorPrimary, + }, + }, + + [`${componentCls}-handle`]: { + position: 'absolute', + width: token.handleSize, + height: token.handleSize, + outline: 'none', + + [`${componentCls}-dragging`]: { + zIndex: 1, + }, + + // 扩大选区 + '&::before': { + content: '""', + position: 'absolute', + insetInlineStart: -token.handleLineWidth, + insetBlockStart: -token.handleLineWidth, + width: token.handleSize + token.handleLineWidth * 2, + height: token.handleSize + token.handleLineWidth * 2, + backgroundColor: 'transparent', + }, + + '&::after': { + content: '""', + position: 'absolute', + insetBlockStart: 0, + insetInlineStart: 0, + width: token.handleSize, + height: token.handleSize, + backgroundColor: token.colorBgElevated, + boxShadow: `0 0 0 ${token.handleLineWidth}px ${token.colorPrimaryBorder}`, + borderRadius: '50%', + cursor: 'pointer', + transition: ` + inset-inline-start ${token.motionDurationMid}, + inset-block-start ${token.motionDurationMid}, + width ${token.motionDurationMid}, + height ${token.motionDurationMid}, + box-shadow ${token.motionDurationMid} + `, + }, + + '&:hover, &:active, &:focus': { + '&::before': { + insetInlineStart: -( + (token.handleSizeHover - token.handleSize) / 2 + + token.handleLineWidthHover + ), + insetBlockStart: -( + (token.handleSizeHover - token.handleSize) / 2 + + token.handleLineWidthHover + ), + width: token.handleSizeHover + token.handleLineWidthHover * 2, + height: token.handleSizeHover + token.handleLineWidthHover * 2, + }, + + '&::after': { + boxShadow: `0 0 0 ${token.handleLineWidthHover}px ${token.colorPrimary}`, + width: token.handleSizeHover, + height: token.handleSizeHover, + insetInlineStart: (token.handleSize - token.handleSizeHover) / 2, + insetBlockStart: (token.handleSize - token.handleSizeHover) / 2, + }, + }, + }, + + [`${componentCls}-mark`]: { + position: 'absolute', + fontSize: token.fontSize, + }, + + [`${componentCls}-mark-text`]: { + position: 'absolute', + display: 'inline-block', + color: token.colorTextDescription, + textAlign: 'center', + wordBreak: 'keep-all', + cursor: 'pointer', + userSelect: 'none', + + '&-active': { + color: token.colorText, + }, + }, + + [`${componentCls}-step`]: { + position: 'absolute', + background: 'transparent', + pointerEvents: 'none', + }, + + [`${componentCls}-dot`]: { + position: 'absolute', + width: dotSize, + height: dotSize, + backgroundColor: token.colorBgElevated, + border: `${token.handleLineWidth}px solid ${token.colorBorderSecondary}`, + borderRadius: '50%', + cursor: 'pointer', + transition: `border-color ${token.motionDurationSlow}`, + + '&-active': { + borderColor: token.colorPrimaryBorder, + }, + }, + + [`&${componentCls}-disabled`]: { + cursor: 'not-allowed', + + [`${componentCls}-rail`]: { + backgroundColor: `${token.colorFillSecondary} !important`, + }, + + [`${componentCls}-track`]: { + backgroundColor: `${token.colorTextDisabled} !important`, + }, + + [` + ${componentCls}-dot + `]: { + backgroundColor: token.colorBgElevated, + borderColor: token.colorTextDisabled, + boxShadow: 'none', + cursor: 'not-allowed', + }, + + [`${componentCls}-handle::after`]: { + backgroundColor: token.colorBgElevated, + cursor: 'not-allowed', + width: token.handleSize, + height: token.handleSize, + boxShadow: `0 0 0 ${token.handleLineWidth}px ${new TinyColor(token.colorTextDisabled) + .onBackground(token.colorBgContainer) + .toHexString()}`, + insetInlineStart: 0, + insetBlockStart: 0, + }, + + [` + ${componentCls}-mark-text, + ${componentCls}-dot + `]: { + cursor: `not-allowed !important`, + }, + }, + }, + }; +}; + +// ============================ Horizontal ============================ +const genDirectionStyle = (token: SliderToken, horizontal: boolean): CSSObject => { + const { componentCls, railSize, handleSize, dotSize } = token; + + const railPadding: keyof CSSProperties = horizontal ? 'paddingBlock' : 'paddingInline'; + const full: keyof CSSProperties = horizontal ? 'width' : 'height'; + const part: keyof CSSProperties = horizontal ? 'height' : 'width'; + const handlePos: keyof CSSProperties = horizontal ? 'insetBlockStart' : 'insetInlineStart'; + const markInset: keyof CSSProperties = horizontal ? 'top' : 'insetInlineStart'; + + return { + [railPadding]: railSize, + [part]: railSize * 3, + + [`${componentCls}-rail`]: { + [full]: '100%', + [part]: railSize, + }, + + [`${componentCls}-track`]: { + [part]: railSize, + }, + + [`${componentCls}-handle`]: { + [handlePos]: (railSize * 3 - handleSize) / 2, + }, + + [`${componentCls}-mark`]: { + // Reset all + insetInlineStart: 0, + top: 0, + [markInset]: handleSize, + [full]: '100%', + }, + + [`${componentCls}-step`]: { + // Reset all + insetInlineStart: 0, + top: 0, + [markInset]: railSize, + [full]: '100%', + [part]: railSize, + }, + + [`${componentCls}-dot`]: { + position: 'absolute', + [handlePos]: (railSize - dotSize) / 2, + }, + }; +}; +// ============================ Horizontal ============================ +const genHorizontalStyle: GenerateStyle = token => { + const { componentCls, marginPartWithMark } = token; + + return { + [`${componentCls}-horizontal`]: { + ...genDirectionStyle(token, true), + + [`&${componentCls}-with-marks`]: { + marginBottom: marginPartWithMark, + }, + }, + }; +}; + +// ============================= Vertical ============================= +const genVerticalStyle: GenerateStyle = token => { + const { componentCls } = token; + + return { + [`${componentCls}-vertical`]: { + ...genDirectionStyle(token, false), + height: '100%', + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook( + 'Slider', + token => { + const sliderToken = mergeToken(token, { + marginPart: (token.controlHeight - token.controlSize) / 2, + marginFull: token.controlSize / 2, + marginPartWithMark: token.controlHeightLG - token.controlSize, + }); + return [ + genBaseStyle(sliderToken), + genHorizontalStyle(sliderToken), + genVerticalStyle(sliderToken), + ]; + }, + token => { + // Handle line width is always width-er 1px + const increaseHandleWidth = 1; + const controlSize = token.controlHeightLG / 4; + const controlSizeHover = token.controlHeightSM / 2; + const handleLineWidth = token.lineWidth + increaseHandleWidth; + const handleLineWidthHover = token.lineWidth + increaseHandleWidth * 3; + return { + controlSize, + railSize: 4, + handleSize: controlSize, + handleSizeHover: controlSizeHover, + dotSize: 8, + handleLineWidth, + handleLineWidthHover, + }; + }, +); diff --git a/components/slider/style/rtl.less b/components/slider/style/rtl.less deleted file mode 100644 index 7dde8a9087..0000000000 --- a/components/slider/style/rtl.less +++ /dev/null @@ -1,70 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@slider-prefix-cls: ~'@{ant-prefix}-slider'; - -.@{slider-prefix-cls} { - &-rtl { - direction: rtl; - } - - &-mark { - .@{slider-prefix-cls}-rtl & { - right: 0; - left: auto; - } - } - - &-dot { - .@{slider-prefix-cls}-rtl & { - margin-right: -4px; - margin-left: 0; - } - - &:first-child { - .@{slider-prefix-cls}-rtl & { - margin-right: -4px; - margin-left: 0; - } - } - - &:last-child { - .@{slider-prefix-cls}-rtl & { - margin-right: -4px; - margin-left: 0; - } - } - } -} - -.vertical() { - &-vertical { - .@{slider-prefix-cls}-handle { - .@{slider-prefix-cls}-rtl& { - margin-right: -5px; - margin-left: 0; - } - } - - .@{slider-prefix-cls}-mark { - .@{slider-prefix-cls}-rtl& { - right: 12px; - left: auto; - } - } - - .@{slider-prefix-cls}-mark-text { - .@{slider-prefix-cls}-rtl& { - right: 4px; - left: auto; - } - } - - .@{slider-prefix-cls}-dot { - .@{slider-prefix-cls}-rtl& { - right: 2px; - left: auto; - } - } - } -} diff --git a/components/style.ts b/components/style.ts index b2d119dea9..8287e17c20 100644 --- a/components/style.ts +++ b/components/style.ts @@ -35,7 +35,7 @@ import './time-picker/style'; import './steps/style'; // import './breadcrumb/style'; import './calendar/style'; -// import './date-picker/style'; +import './date-picker/style'; import './slider/style'; import './table/style'; // import './progress/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index ca1fa3dcfa..752ca9729e 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -33,7 +33,7 @@ import type { ComponentToken as RateComponentToken } from '../../rate/style'; // import type { ComponentToken as SegmentedComponentToken } from '../../segmented/style'; // import type { ComponentToken as SelectComponentToken } from '../../select/style'; import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style'; -// import type { ComponentToken as SliderComponentToken } from '../../slider/style'; +import type { ComponentToken as SliderComponentToken } from '../../slider/style'; import type { ComponentToken as SpaceComponentToken } from '../../space/style'; import type { ComponentToken as SpinComponentToken } from '../../spin/style'; // import type { ComponentToken as StepsComponentToken } from '../../steps/style'; @@ -91,7 +91,7 @@ export interface ComponentTokenMap { // Segmented?: SegmentedComponentToken; // Select?: SelectComponentToken; Skeleton?: SkeletonComponentToken; - // Slider?: SliderComponentToken; + Slider?: SliderComponentToken; Spin?: SpinComponentToken; Statistic?: {}; Switch?: {}; @@ -110,8 +110,8 @@ export interface ComponentTokenMap { // Upload?: UploadComponentToken; Tooltip?: TooltipComponentToken; // Table?: TableComponentToken; - // Space?: SpaceComponentToken; - // Progress?: ProgressComponentToken; + Space?: SpaceComponentToken; + Progress?: ProgressComponentToken; // Tour?: TourComponentToken; // QRCode?: QRCodeComponentToken; // App?: AppComponentToken; diff --git a/components/vc-slider/src/common/createSlider.tsx b/components/vc-slider/src/common/createSlider.tsx index 626ea72d61..0937a85268 100644 --- a/components/vc-slider/src/common/createSlider.tsx +++ b/components/vc-slider/src/common/createSlider.tsx @@ -304,6 +304,7 @@ export default function createSlider(Component) { [`${prefixCls}-with-marks`]: Object.keys(marks).length, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-vertical`]: vertical, + [`${prefixCls}-horizontal`]: !vertical, }); const markProps = { vertical,