From 69c17dc255f9963d17b0723058c1db2213a59607 Mon Sep 17 00:00:00 2001 From: Zev Zhu <45655660+aibayanyu20@users.noreply.github.com> Date: Tue, 7 Feb 2023 23:19:09 +0800 Subject: [PATCH] refactor: switch (#6236) * refactor: switch style * refactor: delete switch style --- components/style.ts | 2 +- components/switch/index.tsx | 86 +++---- components/switch/style/index.less | 155 ------------- components/switch/style/index.ts | 353 +++++++++++++++++++++++++++++ components/switch/style/index.tsx | 2 - components/switch/style/rtl.less | 52 ----- 6 files changed, 400 insertions(+), 250 deletions(-) delete mode 100644 components/switch/style/index.less create mode 100644 components/switch/style/index.ts delete mode 100644 components/switch/style/index.tsx delete mode 100644 components/switch/style/rtl.less diff --git a/components/style.ts b/components/style.ts index 0ab8e11ce..7a657849b 100644 --- a/components/style.ts +++ b/components/style.ts @@ -24,7 +24,7 @@ import './carousel/style'; // import './message/style'; // import './spin/style'; import './select/style'; -import './switch/style'; +// import './switch/style'; import './auto-complete/style'; // import './affix/style'; import './cascader/style'; diff --git a/components/switch/index.tsx b/components/switch/index.tsx index 2efaa1828..17e5e8122 100644 --- a/components/switch/index.tsx +++ b/components/switch/index.tsx @@ -11,7 +11,7 @@ import useConfigInject from '../config-provider/hooks/useConfigInject'; import { useInjectFormItemContext } from '../form/FormItemContext'; import omit from '../_util/omit'; import type { FocusEventHandler } from '../_util/EventInterface'; - +import useStyle from './style'; export const SwitchSizes = tuple('small', 'default'); type CheckedType = boolean | string | number; export const switchProps = () => ({ @@ -89,6 +89,7 @@ const Switch = defineComponent({ ); const { prefixCls, direction, size } = useConfigInject('switch', props); + const [wrapSSR, hashId] = useStyle(prefixCls); const refSwitchNode = ref(); const focus = () => { refSwitchNode.value?.focus(); @@ -148,47 +149,52 @@ const Switch = defineComponent({ [`${prefixCls.value}-disabled`]: props.disabled, [prefixCls.value]: true, [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [hashId.value]: true, })); - return () => ( - - - - ); + return () => + wrapSSR( + + + , + ); }, }); diff --git a/components/switch/style/index.less b/components/switch/style/index.less deleted file mode 100644 index 33a021501..000000000 --- a/components/switch/style/index.less +++ /dev/null @@ -1,155 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@switch-prefix-cls: ~'@{ant-prefix}-switch'; -@switch-duration: 0.2s; - -@switch-pin-size: @switch-height - 4px; -@switch-sm-pin-size: @switch-sm-height - 4px; - -.@{switch-prefix-cls} { - .reset-component(); - - position: relative; - display: inline-block; - box-sizing: border-box; - min-width: @switch-min-width; - height: @switch-height; - line-height: @switch-height; - vertical-align: middle; - background-image: linear-gradient(to right, @disabled-color, @disabled-color), - linear-gradient(to right, @component-background, @component-background); - border: 0; - border-radius: 100px; - cursor: pointer; - transition: all @switch-duration; - user-select: none; - - &:focus { - outline: 0; - box-shadow: 0 0 0 2px fade(@disabled-color, 10%); - } - - &-checked:focus { - box-shadow: 0 0 0 2px @primary-1; - } - - &:focus:hover { - box-shadow: none; - } - - &-checked { - background: @switch-color; - } - - &-loading, - &-disabled { - cursor: not-allowed; - opacity: @switch-disabled-opacity; - - * { - box-shadow: none; - cursor: not-allowed; - } - } - - // ========================= Inner ========================== - &-inner { - display: block; - margin: 0 @switch-inner-margin-min 0 @switch-inner-margin-max; - color: @text-color-inverse; - font-size: @font-size-sm; - transition: margin @switch-duration; - } - - &-checked &-inner { - margin: 0 @switch-inner-margin-max 0 @switch-inner-margin-min; - } - - // ========================= Handle ========================= - &-handle { - position: absolute; - top: @switch-padding; - left: @switch-padding; - width: @switch-pin-size; - height: @switch-pin-size; - transition: all @switch-duration ease-in-out; - - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: @switch-bg; - border-radius: (@switch-pin-size / 2); - box-shadow: 0 2px 4px 0 @switch-shadow-color; - transition: all @switch-duration ease-in-out; - content: ''; - } - } - - &-checked &-handle { - left: calc(100% - @switch-pin-size - @switch-padding); - } - - &:not(&-disabled):active { - .@{switch-prefix-cls}-handle::before { - right: -30%; - left: 0; - } - - &.@{switch-prefix-cls}-checked { - .@{switch-prefix-cls}-handle::before { - right: 0; - left: -30%; - } - } - } - - // ======================== Loading ========================= - &-loading-icon.@{iconfont-css-prefix} { - position: relative; - top: ((@switch-pin-size - @font-size-base) / 2); - color: rgba(0, 0, 0, 0.65); - vertical-align: top; - } - - &-checked &-loading-icon { - color: @switch-color; - } - - // ========================== Size ========================== - &-small { - min-width: @switch-sm-min-width; - height: @switch-sm-height; - line-height: @switch-sm-height; - - .@{switch-prefix-cls}-inner { - margin: 0 @switch-sm-inner-margin-min 0 @switch-sm-inner-margin-max; - font-size: @font-size-sm; - } - - .@{switch-prefix-cls}-handle { - width: @switch-sm-pin-size; - height: @switch-sm-pin-size; - } - - .@{switch-prefix-cls}-loading-icon { - top: ((@switch-sm-pin-size - 9px) / 2); - font-size: 9px; - } - - &.@{switch-prefix-cls}-checked { - .@{switch-prefix-cls}-inner { - margin: 0 @switch-sm-inner-margin-max 0 @switch-sm-inner-margin-min; - } - - .@{switch-prefix-cls}-handle { - left: calc(100% - @switch-sm-pin-size - @switch-padding); - } - } - } -} - -@import './rtl'; diff --git a/components/switch/style/index.ts b/components/switch/style/index.ts new file mode 100644 index 000000000..d72a14cf0 --- /dev/null +++ b/components/switch/style/index.ts @@ -0,0 +1,353 @@ +import type { CSSObject } from '../../_util/cssinjs'; +import { TinyColor } from '@ctrl/tinycolor'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { genFocusStyle, resetComponent } from '../../_style'; + +interface SwitchToken extends FullToken<'Switch'> { + switchMinWidth: number; + switchHeight: number; + switchDuration: string; + switchColor: string; + switchDisabledOpacity: number; + switchInnerMarginMin: number; + switchInnerMarginMax: number; + switchPadding: number; + switchPinSize: number; + switchBg: string; + switchMinWidthSM: number; + switchHeightSM: number; + switchInnerMarginMinSM: number; + switchInnerMarginMaxSM: number; + switchPinSizeSM: number; + switchHandleShadow: string; + switchLoadingIconSize: number; + switchLoadingIconColor: string; + switchHandleActiveInset: string; +} + +const genSwitchSmallStyle: GenerateStyle = token => { + const { componentCls } = token; + const switchInnerCls = `${componentCls}-inner`; + + return { + [componentCls]: { + [`&${componentCls}-small`]: { + minWidth: token.switchMinWidthSM, + height: token.switchHeightSM, + lineHeight: `${token.switchHeightSM}px`, + + [`${componentCls}-inner`]: { + paddingInlineStart: token.switchInnerMarginMaxSM, + paddingInlineEnd: token.switchInnerMarginMinSM, + [`${switchInnerCls}-checked`]: { + marginInlineStart: `calc(-100% + ${ + token.switchPinSizeSM + token.switchPadding * 2 + }px - ${token.switchInnerMarginMaxSM * 2}px)`, + marginInlineEnd: `calc(100% - ${token.switchPinSizeSM + token.switchPadding * 2}px + ${ + token.switchInnerMarginMaxSM * 2 + }px)`, + }, + + [`${switchInnerCls}-unchecked`]: { + marginTop: -token.switchHeightSM, + marginInlineStart: 0, + marginInlineEnd: 0, + }, + }, + + [`${componentCls}-handle`]: { + width: token.switchPinSizeSM, + height: token.switchPinSizeSM, + }, + + [`${componentCls}-loading-icon`]: { + top: (token.switchPinSizeSM - token.switchLoadingIconSize) / 2, + fontSize: token.switchLoadingIconSize, + }, + + [`&${componentCls}-checked`]: { + [`${componentCls}-inner`]: { + paddingInlineStart: token.switchInnerMarginMinSM, + paddingInlineEnd: token.switchInnerMarginMaxSM, + [`${switchInnerCls}-checked`]: { + marginInlineStart: 0, + marginInlineEnd: 0, + }, + + [`${switchInnerCls}-unchecked`]: { + marginInlineStart: `calc(100% - ${ + token.switchPinSizeSM + token.switchPadding * 2 + }px + ${token.switchInnerMarginMaxSM * 2}px)`, + marginInlineEnd: `calc(-100% + ${ + token.switchPinSizeSM + token.switchPadding * 2 + }px - ${token.switchInnerMarginMaxSM * 2}px)`, + }, + }, + + [`${componentCls}-handle`]: { + insetInlineStart: `calc(100% - ${token.switchPinSizeSM + token.switchPadding}px)`, + }, + }, + + [`&:not(${componentCls}-disabled):active`]: { + [`&:not(${componentCls}-checked) ${switchInnerCls}`]: { + [`${switchInnerCls}-unchecked`]: { + marginInlineStart: token.marginXXS / 2, + marginInlineEnd: -token.marginXXS / 2, + }, + }, + + [`&${componentCls}-checked ${switchInnerCls}`]: { + [`${switchInnerCls}-checked`]: { + marginInlineStart: -token.marginXXS / 2, + marginInlineEnd: token.marginXXS / 2, + }, + }, + }, + }, + }, + }; +}; + +const genSwitchLoadingStyle: GenerateStyle = token => { + const { componentCls } = token; + + return { + [componentCls]: { + [`${componentCls}-loading-icon${token.iconCls}`]: { + position: 'relative', + top: (token.switchPinSize - token.fontSize) / 2, + color: token.switchLoadingIconColor, + verticalAlign: 'top', + }, + + [`&${componentCls}-checked ${componentCls}-loading-icon`]: { + color: token.switchColor, + }, + }, + }; +}; + +const genSwitchHandleStyle: GenerateStyle = token => { + const { componentCls } = token; + const switchHandleCls = `${componentCls}-handle`; + + return { + [componentCls]: { + [switchHandleCls]: { + position: 'absolute', + top: token.switchPadding, + insetInlineStart: token.switchPadding, + width: token.switchPinSize, + height: token.switchPinSize, + transition: `all ${token.switchDuration} ease-in-out`, + + '&::before': { + position: 'absolute', + top: 0, + insetInlineEnd: 0, + bottom: 0, + insetInlineStart: 0, + backgroundColor: token.colorWhite, + borderRadius: token.switchPinSize / 2, + boxShadow: token.switchHandleShadow, + transition: `all ${token.switchDuration} ease-in-out`, + content: '""', + }, + }, + + [`&${componentCls}-checked ${switchHandleCls}`]: { + insetInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding}px)`, + }, + + [`&:not(${componentCls}-disabled):active`]: { + [`${switchHandleCls}::before`]: { + insetInlineEnd: token.switchHandleActiveInset, + insetInlineStart: 0, + }, + + [`&${componentCls}-checked ${switchHandleCls}::before`]: { + insetInlineEnd: 0, + insetInlineStart: token.switchHandleActiveInset, + }, + }, + }, + }; +}; + +const genSwitchInnerStyle: GenerateStyle = token => { + const { componentCls } = token; + const switchInnerCls = `${componentCls}-inner`; + + return { + [componentCls]: { + [switchInnerCls]: { + display: 'block', + overflow: 'hidden', + borderRadius: 100, + height: '100%', + paddingInlineStart: token.switchInnerMarginMax, + paddingInlineEnd: token.switchInnerMarginMin, + transition: `padding-inline-start ${token.switchDuration} ease-in-out, padding-inline-end ${token.switchDuration} ease-in-out`, + + [`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: { + display: 'block', + color: token.colorTextLightSolid, + fontSize: token.fontSizeSM, + transition: `margin-inline-start ${token.switchDuration} ease-in-out, margin-inline-end ${token.switchDuration} ease-in-out`, + pointerEvents: 'none', + }, + + [`${switchInnerCls}-checked`]: { + marginInlineStart: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${ + token.switchInnerMarginMax * 2 + }px)`, + marginInlineEnd: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${ + token.switchInnerMarginMax * 2 + }px)`, + }, + + [`${switchInnerCls}-unchecked`]: { + marginTop: -token.switchHeight, + marginInlineStart: 0, + marginInlineEnd: 0, + }, + }, + + [`&${componentCls}-checked ${switchInnerCls}`]: { + paddingInlineStart: token.switchInnerMarginMin, + paddingInlineEnd: token.switchInnerMarginMax, + [`${switchInnerCls}-checked`]: { + marginInlineStart: 0, + marginInlineEnd: 0, + }, + + [`${switchInnerCls}-unchecked`]: { + marginInlineStart: `calc(100% - ${token.switchPinSize + token.switchPadding * 2}px + ${ + token.switchInnerMarginMax * 2 + }px)`, + marginInlineEnd: `calc(-100% + ${token.switchPinSize + token.switchPadding * 2}px - ${ + token.switchInnerMarginMax * 2 + }px)`, + }, + }, + + [`&:not(${componentCls}-disabled):active`]: { + [`&:not(${componentCls}-checked) ${switchInnerCls}`]: { + [`${switchInnerCls}-unchecked`]: { + marginInlineStart: token.switchPadding * 2, + marginInlineEnd: -token.switchPadding * 2, + }, + }, + + [`&${componentCls}-checked ${switchInnerCls}`]: { + [`${switchInnerCls}-checked`]: { + marginInlineStart: -token.switchPadding * 2, + marginInlineEnd: token.switchPadding * 2, + }, + }, + }, + }, + }; +}; + +const genSwitchStyle = (token: SwitchToken): CSSObject => { + const { componentCls } = token; + + return { + [componentCls]: { + ...resetComponent(token), + + position: 'relative', + display: 'inline-block', + boxSizing: 'border-box', + minWidth: token.switchMinWidth, + height: token.switchHeight, + lineHeight: `${token.switchHeight}px`, + verticalAlign: 'middle', + background: token.colorTextQuaternary, + border: '0', + borderRadius: 100, + cursor: 'pointer', + transition: `all ${token.motionDurationMid}`, + userSelect: 'none', + + [`&:hover:not(${componentCls}-disabled)`]: { + background: token.colorTextTertiary, + }, + + ...genFocusStyle(token), + + [`&${componentCls}-checked`]: { + background: token.switchColor, + + [`&:hover:not(${componentCls}-disabled)`]: { + background: token.colorPrimaryHover, + }, + }, + + [`&${componentCls}-loading, &${componentCls}-disabled`]: { + cursor: 'not-allowed', + opacity: token.switchDisabledOpacity, + + '*': { + boxShadow: 'none', + cursor: 'not-allowed', + }, + }, + + // rtl style + [`&${componentCls}-rtl`]: { + direction: 'rtl', + }, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Switch', token => { + const switchHeight = token.fontSize * token.lineHeight; + const switchHeightSM = token.controlHeight / 2; + const switchPadding = 2; // This is magic + const switchPinSize = switchHeight - switchPadding * 2; + const switchPinSizeSM = switchHeightSM - switchPadding * 2; + + const switchToken = mergeToken(token, { + switchMinWidth: switchPinSize * 2 + switchPadding * 4, + switchHeight, + switchDuration: token.motionDurationMid, + switchColor: token.colorPrimary, + switchDisabledOpacity: token.opacityLoading, + switchInnerMarginMin: switchPinSize / 2, + switchInnerMarginMax: switchPinSize + switchPadding + switchPadding * 2, + switchPadding, + switchPinSize, + switchBg: token.colorBgContainer, + switchMinWidthSM: switchPinSizeSM * 2 + switchPadding * 2, + switchHeightSM, + switchInnerMarginMinSM: switchPinSizeSM / 2, + switchInnerMarginMaxSM: switchPinSizeSM + switchPadding + switchPadding * 2, + switchPinSizeSM, + switchHandleShadow: `0 2px 4px 0 ${new TinyColor('#00230b').setAlpha(0.2).toRgbString()}`, + switchLoadingIconSize: token.fontSizeIcon * 0.75, + switchLoadingIconColor: `rgba(0, 0, 0, ${token.opacityLoading})`, + switchHandleActiveInset: '-30%', + }); + + return [ + genSwitchStyle(switchToken), + + // inner style + genSwitchInnerStyle(switchToken), + + // handle style + genSwitchHandleStyle(switchToken), + + // loading style + genSwitchLoadingStyle(switchToken), + + // small style + genSwitchSmallStyle(switchToken), + ]; +}); diff --git a/components/switch/style/index.tsx b/components/switch/style/index.tsx deleted file mode 100644 index 3a3ab0de5..000000000 --- a/components/switch/style/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import '../../style/index.less'; -import './index.less'; diff --git a/components/switch/style/rtl.less b/components/switch/style/rtl.less deleted file mode 100644 index 7a7de94bb..000000000 --- a/components/switch/style/rtl.less +++ /dev/null @@ -1,52 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@switch-prefix-cls: ~'@{ant-prefix}-switch'; - -@switch-pin-size: @switch-height - 4px; -@switch-sm-pin-size: @switch-sm-height - 4px; - -.@{switch-prefix-cls}-rtl { - direction: rtl; - - .@{switch-prefix-cls}-inner { - margin: 0 @switch-inner-margin-max 0 @switch-inner-margin-min; - } - - .@{switch-prefix-cls}-handle { - right: @switch-padding; - left: auto; - } - - &:not(&-disabled):active { - .@{switch-prefix-cls}-handle::before { - right: 0; - left: -30%; - } - - &.@{switch-prefix-cls}-checked { - .@{switch-prefix-cls}-handle::before { - right: -30%; - left: 0; - } - } - } - - &.@{switch-prefix-cls}-checked { - .@{switch-prefix-cls}-inner { - margin: 0 @switch-inner-margin-min 0 @switch-inner-margin-max; - } - - .@{switch-prefix-cls}-handle { - right: calc(100% - @switch-pin-size - @switch-padding); - } - } - - &.@{switch-prefix-cls}-small { - &.@{switch-prefix-cls}-checked { - .@{switch-prefix-cls}-handle { - right: calc(100% - @switch-sm-pin-size - @switch-padding); - } - } - } -}