From 36896806bbee5bfc8ce6ad4075129c2d1a2d6789 Mon Sep 17 00:00:00 2001 From: shifeng1993 Date: Tue, 21 Feb 2023 00:59:11 +0800 Subject: [PATCH 1/2] refactor:radio --- components/radio/Group.tsx | 44 +- components/radio/Radio.tsx | 53 ++- components/radio/RadioButton.tsx | 2 +- components/radio/index.en-US.md | 2 +- components/radio/index.zh-CN.md | 2 +- components/radio/style/index.less | 366 --------------- components/radio/style/index.tsx | 548 ++++++++++++++++++++++- components/radio/style/rtl.less | 61 --- components/style.ts | 2 +- components/theme/interface/components.ts | 4 +- 10 files changed, 611 insertions(+), 473 deletions(-) delete mode 100644 components/radio/style/index.less delete mode 100644 components/radio/style/rtl.less diff --git a/components/radio/Group.tsx b/components/radio/Group.tsx index 2e31ccf7b9..2c50bf7324 100644 --- a/components/radio/Group.tsx +++ b/components/radio/Group.tsx @@ -1,5 +1,5 @@ import { nextTick, defineComponent, ref, watch, computed } from 'vue'; -import type { PropType, ExtractPropTypes } from 'vue'; +import type { ExtractPropTypes } from 'vue'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; import Radio from './Radio'; @@ -8,6 +8,10 @@ import { tuple } from '../_util/type'; import type { RadioChangeEvent, RadioGroupButtonStyle, RadioGroupOptionType } from './interface'; import { useInjectFormItemContext } from '../form/FormItemContext'; import { useProvideRadioGroupContext } from './context'; +import { booleanType, stringType, arrayType, functionType } from '../_util/type'; + +// CSSINJS +import useStyle from './style'; const RadioGroupSizeTypes = tuple('large', 'default', 'small'); @@ -25,16 +29,14 @@ export const radioGroupProps = () => ({ prefixCls: String, value: PropTypes.any, size: PropTypes.oneOf(RadioGroupSizeTypes), - options: { - type: Array as PropType>, - }, - disabled: { type: Boolean, default: undefined }, + options: arrayType>(), + disabled: booleanType(), name: String, - buttonStyle: { type: String as PropType, default: 'outline' }, + buttonStyle: stringType('outline'), id: String, - optionType: { type: String as PropType, default: 'default' }, - onChange: Function as PropType<(e: RadioChangeEvent) => void>, - 'onUpdate:value': Function as PropType<(val: any) => void>, + optionType: stringType('default'), + onChange: functionType<(e: RadioChangeEvent) => void>(), + 'onUpdate:value': functionType<(val: any) => void>(), }); export type RadioGroupProps = Partial>>; @@ -44,9 +46,13 @@ export default defineComponent({ name: 'ARadioGroup', props: radioGroupProps(), // emits: ['update:value', 'change'], - setup(props, { slots, emit }) { + setup(props, { slots, emit, attrs }) { const formItemContext = useInjectFormItemContext(); const { prefixCls, direction, size } = useConfigInject('radio', props); + + // Style + const [wrapSSR, hashId] = useStyle(prefixCls); + const stateValue = ref(props.value); const updatingValue = ref(false); watch( @@ -89,10 +95,16 @@ export default defineComponent({ const groupPrefixCls = `${prefixCls.value}-group`; - const classString = classNames(groupPrefixCls, `${groupPrefixCls}-${buttonStyle}`, { - [`${groupPrefixCls}-${size.value}`]: size.value, - [`${groupPrefixCls}-rtl`]: direction.value === 'rtl', - }); + const classString = classNames( + groupPrefixCls, + `${groupPrefixCls}-${buttonStyle}`, + { + [`${groupPrefixCls}-${size.value}`]: size.value, + [`${groupPrefixCls}-rtl`]: direction.value === 'rtl', + }, + attrs.class, + hashId.value, + ); let children = null; if (options && options.length > 0) { @@ -126,10 +138,10 @@ export default defineComponent({ } else { children = slots.default?.(); } - return ( + return wrapSSR(
{children} -
+ , ); }; }, diff --git a/components/radio/Radio.tsx b/components/radio/Radio.tsx index 07fa0b0de3..b6987c46ea 100644 --- a/components/radio/Radio.tsx +++ b/components/radio/Radio.tsx @@ -1,4 +1,4 @@ -import type { ExtractPropTypes, PropType } from 'vue'; +import type { ExtractPropTypes } from 'vue'; import { computed, defineComponent, ref } from 'vue'; import PropTypes from '../_util/vue-types'; import VcCheckbox from '../vc-checkbox/Checkbox'; @@ -9,22 +9,26 @@ import { FormItemInputContext, useInjectFormItemContext } from '../form/FormItem import omit from '../_util/omit'; import type { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'; import { useInjectRadioGroupContext, useInjectRadioOptionTypeContext } from './context'; +import { booleanType, functionType } from '../_util/type'; + +// CSSINJS +import useStyle from './style'; export const radioProps = () => ({ prefixCls: String, - checked: { type: Boolean, default: undefined }, - disabled: { type: Boolean, default: undefined }, - isGroup: { type: Boolean, default: undefined }, + checked: booleanType(), + disabled: booleanType(), + isGroup: booleanType(), value: PropTypes.any, name: String, id: String, - autofocus: { type: Boolean, default: undefined }, - onChange: Function as PropType<(event: RadioChangeEvent) => void>, - onFocus: Function as PropType, - onBlur: Function as PropType, - onClick: Function as PropType, - 'onUpdate:checked': Function as PropType<(checked: boolean) => void>, - 'onUpdate:value': Function as PropType<(checked: boolean) => void>, + autofocus: booleanType(), + onChange: functionType<(event: RadioChangeEvent) => void>(), + onFocus: functionType(), + onBlur: functionType(), + onClick: functionType(), + 'onUpdate:checked': functionType<(checked: boolean) => void>(), + 'onUpdate:value': functionType<(checked: boolean) => void>(), }); export type RadioProps = Partial>>; @@ -42,10 +46,14 @@ export default defineComponent({ const { prefixCls: radioPrefixCls, direction } = useConfigInject('radio', props); const prefixCls = computed(() => - (radioGroupContext?.optionType.value || radioOptionTypeContext) === 'button' + radioGroupContext?.optionType.value === 'button' || radioOptionTypeContext === 'button' ? `${radioPrefixCls.value}-button` : radioPrefixCls.value, ); + + // Style + const [wrapSSR, hashId] = useStyle(radioPrefixCls); + const focus = () => { vcCheckbox.value.focus(); }; @@ -89,19 +97,22 @@ export default defineComponent({ } else { rProps.onChange = handleChange; } - const wrapperClassString = classNames({ - [`${prefixCls.value}-wrapper`]: true, - [`${prefixCls.value}-wrapper-checked`]: rProps.checked, - [`${prefixCls.value}-wrapper-disabled`]: rProps.disabled, - [`${prefixCls.value}-wrapper-rtl`]: direction.value === 'rtl', - [`${prefixCls.value}-wrapper-in-form-item`]: formItemInputContext.isFormItemInput, - }); + const wrapperClassString = classNames( + { + [`${prefixCls.value}-wrapper`]: true, + [`${prefixCls.value}-wrapper-checked`]: rProps.checked, + [`${prefixCls.value}-wrapper-disabled`]: rProps.disabled, + [`${prefixCls.value}-wrapper-rtl`]: direction.value === 'rtl', + [`${prefixCls.value}-wrapper-in-form-item`]: formItemInputContext.isFormItemInput, + }, + hashId.value, + ); - return ( + return wrapSSR( + , ); }; }, diff --git a/components/radio/RadioButton.tsx b/components/radio/RadioButton.tsx index cfe9687da0..1177956296 100644 --- a/components/radio/RadioButton.tsx +++ b/components/radio/RadioButton.tsx @@ -8,7 +8,7 @@ export default defineComponent({ name: 'ARadioButton', props: radioProps(), setup(props, { slots }) { - const { prefixCls } = useConfigInject('radio-button', props); + const { prefixCls } = useConfigInject('radio', props); useProvideRadioOptionTypeContext('button'); return () => { return ( diff --git a/components/radio/index.en-US.md b/components/radio/index.en-US.md index a1acb67911..9c0bc5a08e 100644 --- a/components/radio/index.en-US.md +++ b/components/radio/index.en-US.md @@ -2,7 +2,7 @@ category: Components type: Data Entry title: Radio -cover: https://gw.alipayobjects.com/zos/alicdn/8cYb5seNB/Radio.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*M-YKTJnWM2kAAAAAAAAAAAAADrJ8AQ/original --- Radio. diff --git a/components/radio/index.zh-CN.md b/components/radio/index.zh-CN.md index 45b4ee72fd..a01da40ff5 100644 --- a/components/radio/index.zh-CN.md +++ b/components/radio/index.zh-CN.md @@ -3,7 +3,7 @@ category: Components type: 数据录入 title: Radio subtitle: 单选框 -cover: https://gw.alipayobjects.com/zos/alicdn/8cYb5seNB/Radio.svg +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*M-YKTJnWM2kAAAAAAAAAAAAADrJ8AQ/original --- 单选框。 diff --git a/components/radio/style/index.less b/components/radio/style/index.less deleted file mode 100644 index 3578015906..0000000000 --- a/components/radio/style/index.less +++ /dev/null @@ -1,366 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@radio-prefix-cls: ~'@{ant-prefix}-radio'; -@radio-group-prefix-cls: ~'@{radio-prefix-cls}-group'; -@radio-inner-prefix-cls: ~'@{radio-prefix-cls}-inner'; -@radio-duration: 0.3s; -@radio-focus-shadow: 0 0 0 3px @primary-1; -@radio-button-focus-shadow: @radio-focus-shadow; - -.@{radio-group-prefix-cls} { - .reset-component(); - - display: inline-block; - font-size: 0; - - .@{ant-prefix}-badge-count { - z-index: 1; - } - - > .@{ant-prefix}-badge:not(:first-child) > .@{radio-prefix-cls}-button-wrapper { - border-left: none; - } -} - -// 一般状态 -.@{radio-prefix-cls}-wrapper { - .reset-component(); - position: relative; - display: inline-flex; - align-items: baseline; - margin-right: @radio-wrapper-margin-right; - cursor: pointer; - - &-disabled { - cursor: not-allowed; - } - - &::after { - display: inline-block; - width: 0; - overflow: hidden; - content: '\a0'; - } - - &&-in-form-item { - input[type='radio'] { - width: 14px; - height: 14px; - } - } -} - -.@{radio-prefix-cls} { - .reset-component(); - - position: relative; - top: @radio-top; - display: inline-block; - outline: none; - cursor: pointer; - - .@{radio-prefix-cls}-wrapper:hover &, - &:hover .@{radio-inner-prefix-cls}, - &-input:focus + .@{radio-inner-prefix-cls} { - border-color: @radio-dot-color; - } - - &-input:focus + .@{radio-inner-prefix-cls} { - box-shadow: @radio-focus-shadow; - } - - &-checked::after { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 1px solid @radio-dot-color; - border-radius: 50%; - visibility: hidden; - animation: antRadioEffect 0.36s ease-in-out; - animation-fill-mode: both; - content: ''; - } - - &:hover::after, - .@{radio-prefix-cls}-wrapper:hover &::after { - visibility: visible; - } - - &-inner { - &::after { - position: absolute; - top: 50%; - left: 50%; - display: block; - width: @radio-size; - height: @radio-size; - margin-top: -(@radio-size / 2); - margin-left: -(@radio-size / 2); - background-color: @radio-dot-color; - border-top: 0; - border-left: 0; - border-radius: @radio-size; - transform: scale(0); - opacity: 0; - transition: all @radio-duration @ease-in-out-circ; - content: ' '; - } - - position: relative; - top: 0; - left: 0; - display: block; - width: @radio-size; - height: @radio-size; - background-color: @radio-button-bg; - border-color: @border-color-base; - border-style: solid; - border-width: @radio-border-width; - border-radius: 50%; - transition: all @radio-duration; - } - - &-input { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - cursor: pointer; - opacity: 0; - } -} - -// 选中状态 -.@{radio-prefix-cls}-checked { - .@{radio-inner-prefix-cls} { - border-color: @radio-dot-color; - - &::after { - transform: scale((unit(@radio-dot-size) / unit(@radio-size))); - opacity: 1; - transition: all @radio-duration @ease-in-out-circ; - } - } -} - -.@{radio-prefix-cls}-disabled { - cursor: not-allowed; - - .@{radio-inner-prefix-cls} { - background-color: @input-disabled-bg; - border-color: @border-color-base !important; - cursor: not-allowed; - - &::after { - background-color: @radio-dot-disabled-color; - } - } - - .@{radio-prefix-cls}-input { - cursor: not-allowed; - } - - & + span { - color: @disabled-color; - cursor: not-allowed; - } -} - -span.@{radio-prefix-cls} + * { - padding-right: 8px; - padding-left: 8px; -} - -.@{radio-prefix-cls}-button-wrapper { - position: relative; - display: inline-block; - height: @btn-height-base; - margin: 0; - padding: 0 @radio-button-padding-horizontal; - color: @radio-button-color; - font-size: @font-size-base; - line-height: @btn-height-base - 2px; - background: @radio-button-bg; - border: @border-width-base @border-style-base @border-color-base; - // strange align fix for chrome but works - // https://gw.alipayobjects.com/zos/rmsportal/VFTfKXJuogBAXcvfAUWJ.gif - border-top-width: @border-width-base + 0.02px; - border-left-width: 0; - cursor: pointer; - transition: color 0.3s, background 0.3s, border-color 0.3s, box-shadow 0.3s; - - a { - color: @radio-button-color; - } - - > .@{radio-prefix-cls}-button { - position: absolute; - top: 0; - left: 0; - z-index: -1; - width: 100%; - height: 100%; - } - - .@{radio-group-prefix-cls}-large & { - height: @input-height-lg; - font-size: @font-size-lg; - line-height: @input-height-lg - 2px; - } - - .@{radio-group-prefix-cls}-small & { - height: @input-height-sm; - padding: 0 @control-padding-horizontal-sm - 1px; - line-height: @input-height-sm - 2px; - } - - &:not(:first-child) { - &::before { - position: absolute; - top: @border-width-base * -1; - left: -1px; - display: block; - box-sizing: content-box; - width: 1px; - height: 100%; - padding: @border-width-base 0; - background-color: @border-color-base; - transition: background-color 0.3s; - content: ''; - } - } - - &:first-child { - border-left: @border-width-base @border-style-base @border-color-base; - border-radius: @border-radius-base 0 0 @border-radius-base; - } - - &:last-child { - border-radius: 0 @border-radius-base @border-radius-base 0; - } - - &:first-child:last-child { - border-radius: @border-radius-base; - } - - &:hover { - position: relative; - color: @radio-dot-color; - } - - &:focus-within { - box-shadow: @radio-button-focus-shadow; - } - - .@{radio-prefix-cls}-inner, - input[type='checkbox'], - input[type='radio'] { - width: 0; - height: 0; - opacity: 0; - pointer-events: none; - } - - &-checked:not(&-disabled) { - z-index: 1; - color: @radio-dot-color; - background: @radio-button-checked-bg; - border-color: @radio-dot-color; - - &::before { - background-color: @radio-dot-color; - } - - &:first-child { - border-color: @radio-dot-color; - } - - &:hover { - color: @radio-button-hover-color; - border-color: @radio-button-hover-color; - - &::before { - background-color: @radio-button-hover-color; - } - } - - &:active { - color: @radio-button-active-color; - border-color: @radio-button-active-color; - - &::before { - background-color: @radio-button-active-color; - } - } - - &:focus-within { - box-shadow: @radio-button-focus-shadow; - } - } - - .@{radio-group-prefix-cls}-solid &-checked:not(&-disabled) { - color: @radio-solid-checked-color; - background: @radio-dot-color; - border-color: @radio-dot-color; - - &:hover { - color: @radio-solid-checked-color; - background: @radio-button-hover-color; - border-color: @radio-button-hover-color; - } - - &:active { - color: @radio-solid-checked-color; - background: @radio-button-active-color; - border-color: @radio-button-active-color; - } - - &:focus-within { - box-shadow: @radio-button-focus-shadow; - } - } - - &-disabled { - color: @disabled-color; - background-color: @input-disabled-bg; - border-color: @border-color-base; - cursor: not-allowed; - - &:first-child, - &:hover { - color: @disabled-color; - background-color: @input-disabled-bg; - border-color: @border-color-base; - } - - &:first-child { - border-left-color: @border-color-base; - } - } - - &-disabled&-checked { - color: @radio-disabled-button-checked-color; - background-color: @radio-disabled-button-checked-bg; - border-color: @border-color-base; - box-shadow: none; - } -} - -@keyframes antRadioEffect { - 0% { - transform: scale(1); - opacity: 0.5; - } - - 100% { - transform: scale(1.6); - opacity: 0; - } -} - -@import './rtl'; diff --git a/components/radio/style/index.tsx b/components/radio/style/index.tsx index d53a9fa2e6..88b6810009 100644 --- a/components/radio/style/index.tsx +++ b/components/radio/style/index.tsx @@ -1,3 +1,545 @@ -import '../../style/index.less'; -import './index.less'; -// deps-lint-skip: form +import { Keyframes } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { genFocusOutline, resetComponent } from '../../_style'; + +// ============================== Tokens ============================== +export interface ComponentToken {} + +interface RadioToken extends FullToken<'Radio'> { + radioFocusShadow: string; + radioButtonFocusShadow: string; + + radioSize: number; + radioTop: number; + radioDotSize: number; + radioDotDisabledSize: number; + radioCheckedColor: string; + radioDotDisabledColor: string; + radioSolidCheckedColor: string; + + radioButtonBg: string; + radioButtonCheckedBg: string; + radioButtonColor: string; + radioButtonHoverColor: string; + radioButtonActiveColor: string; + radioButtonPaddingHorizontal: number; + radioDisabledButtonCheckedBg: string; + radioDisabledButtonCheckedColor: string; + radioWrapperMarginRight: number; +} + +// ============================== Styles ============================== +const antRadioEffect = new Keyframes('antRadioEffect', { + '0%': { transform: 'scale(1)', opacity: 0.5 }, + '100%': { transform: 'scale(1.6)', opacity: 0 }, +}); + +// styles from RadioGroup only +const getGroupRadioStyle: GenerateStyle = token => { + const { componentCls, antCls } = token; + const groupPrefixCls = `${componentCls}-group`; + + return { + [groupPrefixCls]: { + ...resetComponent(token), + display: 'inline-block', + fontSize: 0, + + // RTL + [`&${groupPrefixCls}-rtl`]: { + direction: 'rtl', + }, + + [`${antCls}-badge ${antCls}-badge-count`]: { + zIndex: 1, + }, + + [`> ${antCls}-badge:not(:first-child) > ${antCls}-button-wrapper`]: { + borderInlineStart: 'none', + }, + }, + }; +}; + +// Styles from radio-wrapper +const getRadioBasicStyle: GenerateStyle = token => { + const { + componentCls, + radioWrapperMarginRight, + radioCheckedColor, + radioSize, + motionDurationSlow, + motionDurationMid, + motionEaseInOut, + motionEaseInOutCirc, + radioButtonBg, + colorBorder, + lineWidth, + radioDotSize, + colorBgContainerDisabled, + colorTextDisabled, + paddingXS, + radioDotDisabledColor, + lineType, + radioDotDisabledSize, + wireframe, + colorWhite, + } = token; + const radioInnerPrefixCls = `${componentCls}-inner`; + + return { + [`${componentCls}-wrapper`]: { + ...resetComponent(token), + position: 'relative', + display: 'inline-flex', + alignItems: 'baseline', + marginInlineStart: 0, + marginInlineEnd: radioWrapperMarginRight, + cursor: 'pointer', + + // RTL + [`&${componentCls}-wrapper-rtl`]: { + direction: 'rtl', + }, + + '&-disabled': { + cursor: 'not-allowed', + color: token.colorTextDisabled, + }, + + '&::after': { + display: 'inline-block', + width: 0, + overflow: 'hidden', + content: '"\\a0"', + }, + + // hashId 在 wrapper 上,只能铺平 + [`${componentCls}-checked::after`]: { + position: 'absolute', + insetBlockStart: 0, + insetInlineStart: 0, + width: '100%', + height: '100%', + border: `${lineWidth}px ${lineType} ${radioCheckedColor}`, + borderRadius: '50%', + visibility: 'hidden', + animationName: antRadioEffect, + animationDuration: motionDurationSlow, + animationTimingFunction: motionEaseInOut, + animationFillMode: 'both', + content: '""', + }, + + [componentCls]: { + ...resetComponent(token), + position: 'relative', + display: 'inline-block', + outline: 'none', + cursor: 'pointer', + alignSelf: 'center', + }, + + [`${componentCls}-wrapper:hover &, + &:hover ${radioInnerPrefixCls}`]: { + borderColor: radioCheckedColor, + }, + + [`${componentCls}-input:focus-visible + ${radioInnerPrefixCls}`]: { + ...genFocusOutline(token), + }, + + [`${componentCls}:hover::after, ${componentCls}-wrapper:hover &::after`]: { + visibility: 'visible', + }, + + [`${componentCls}-inner`]: { + '&::after': { + boxSizing: 'border-box', + position: 'absolute', + insetBlockStart: '50%', + insetInlineStart: '50%', + display: 'block', + width: radioSize, + height: radioSize, + marginBlockStart: radioSize / -2, + marginInlineStart: radioSize / -2, + backgroundColor: wireframe ? radioCheckedColor : colorWhite, + borderBlockStart: 0, + borderInlineStart: 0, + borderRadius: radioSize, + transform: 'scale(0)', + opacity: 0, + transition: `all ${motionDurationSlow} ${motionEaseInOutCirc}`, + content: '""', + }, + + boxSizing: 'border-box', + position: 'relative', + insetBlockStart: 0, + insetInlineStart: 0, + display: 'block', + width: radioSize, + height: radioSize, + backgroundColor: radioButtonBg, + borderColor: colorBorder, + borderStyle: 'solid', + borderWidth: lineWidth, + borderRadius: '50%', + transition: `all ${motionDurationMid}`, + }, + + [`${componentCls}-input`]: { + position: 'absolute', + insetBlockStart: 0, + insetInlineEnd: 0, + insetBlockEnd: 0, + insetInlineStart: 0, + zIndex: 1, + cursor: 'pointer', + opacity: 0, + }, + + // 选中状态 + [`${componentCls}-checked`]: { + [radioInnerPrefixCls]: { + borderColor: radioCheckedColor, + backgroundColor: wireframe ? radioButtonBg : radioCheckedColor, + + '&::after': { + transform: `scale(${radioDotSize / radioSize})`, + opacity: 1, + transition: `all ${motionDurationSlow} ${motionEaseInOutCirc}`, + }, + }, + }, + + [`${componentCls}-disabled`]: { + cursor: 'not-allowed', + + [radioInnerPrefixCls]: { + backgroundColor: colorBgContainerDisabled, + borderColor: colorBorder, + cursor: 'not-allowed', + + '&::after': { + backgroundColor: radioDotDisabledColor, + }, + }, + + [`${componentCls}-input`]: { + cursor: 'not-allowed', + }, + + [`${componentCls}-disabled + span`]: { + color: colorTextDisabled, + cursor: 'not-allowed', + }, + + [`&${componentCls}-checked`]: { + [radioInnerPrefixCls]: { + '&::after': { + transform: `scale(${radioDotDisabledSize / radioSize})`, + }, + }, + }, + }, + + [`span${componentCls} + *`]: { + paddingInlineStart: paddingXS, + paddingInlineEnd: paddingXS, + }, + }, + }; +}; + +// Styles from radio-button +const getRadioButtonStyle: GenerateStyle = token => { + const { + radioButtonColor, + controlHeight, + componentCls, + lineWidth, + lineType, + colorBorder, + motionDurationSlow, + motionDurationMid, + radioButtonPaddingHorizontal, + fontSize, + radioButtonBg, + fontSizeLG, + controlHeightLG, + controlHeightSM, + paddingXS, + borderRadius, + borderRadiusSM, + borderRadiusLG, + radioCheckedColor, + radioButtonCheckedBg, + radioButtonHoverColor, + radioButtonActiveColor, + radioSolidCheckedColor, + colorTextDisabled, + colorBgContainerDisabled, + radioDisabledButtonCheckedColor, + radioDisabledButtonCheckedBg, + } = token; + return { + [`${componentCls}-button-wrapper`]: { + position: 'relative', + display: 'inline-block', + height: controlHeight, + margin: 0, + paddingInline: radioButtonPaddingHorizontal, + paddingBlock: 0, + color: radioButtonColor, + fontSize, + lineHeight: `${controlHeight - lineWidth * 2}px`, + background: radioButtonBg, + border: `${lineWidth}px ${lineType} ${colorBorder}`, + // strange align fix for chrome but works + // https://gw.alipayobjects.com/zos/rmsportal/VFTfKXJuogBAXcvfAUWJ.gif + borderBlockStartWidth: lineWidth + 0.02, + borderInlineStartWidth: 0, + borderInlineEndWidth: lineWidth, + cursor: 'pointer', + transition: [ + `color ${motionDurationMid}`, + `background ${motionDurationMid}`, + `border-color ${motionDurationMid}`, + `box-shadow ${motionDurationMid}`, + ].join(','), + + a: { + color: radioButtonColor, + }, + + [`> ${componentCls}-button`]: { + position: 'absolute', + insetBlockStart: 0, + insetInlineStart: 0, + zIndex: -1, + width: '100%', + height: '100%', + }, + + '&:not(:first-child)': { + '&::before': { + position: 'absolute', + insetBlockStart: -lineWidth, + insetInlineStart: -lineWidth, + display: 'block', + boxSizing: 'content-box', + width: 1, + height: '100%', + paddingBlock: lineWidth, + paddingInline: 0, + backgroundColor: colorBorder, + transition: `background-color ${motionDurationSlow}`, + content: '""', + }, + }, + + '&:first-child': { + borderInlineStart: `${lineWidth}px ${lineType} ${colorBorder}`, + borderStartStartRadius: borderRadius, + borderEndStartRadius: borderRadius, + }, + + '&:last-child': { + borderStartEndRadius: borderRadius, + borderEndEndRadius: borderRadius, + }, + + '&:first-child:last-child': { + borderRadius, + }, + + [`${componentCls}-group-large &`]: { + height: controlHeightLG, + fontSize: fontSizeLG, + lineHeight: `${controlHeightLG - lineWidth * 2}px`, + + '&:first-child': { + borderStartStartRadius: borderRadiusLG, + borderEndStartRadius: borderRadiusLG, + }, + + '&:last-child': { + borderStartEndRadius: borderRadiusLG, + borderEndEndRadius: borderRadiusLG, + }, + }, + + [`${componentCls}-group-small &`]: { + height: controlHeightSM, + paddingInline: paddingXS - lineWidth, + paddingBlock: 0, + lineHeight: `${controlHeightSM - lineWidth * 2}px`, + + '&:first-child': { + borderStartStartRadius: borderRadiusSM, + borderEndStartRadius: borderRadiusSM, + }, + + '&:last-child': { + borderStartEndRadius: borderRadiusSM, + borderEndEndRadius: borderRadiusSM, + }, + }, + + '&:hover': { + position: 'relative', + color: radioCheckedColor, + }, + + '&:has(:focus-visible)': { + ...genFocusOutline(token), + }, + + [`${componentCls}-inner, input[type='checkbox'], input[type='radio']`]: { + width: 0, + height: 0, + opacity: 0, + pointerEvents: 'none', + }, + + [`&-checked:not(${componentCls}-button-wrapper-disabled)`]: { + zIndex: 1, + color: radioCheckedColor, + background: radioButtonCheckedBg, + borderColor: radioCheckedColor, + + '&::before': { + backgroundColor: radioCheckedColor, + }, + + '&:first-child': { + borderColor: radioCheckedColor, + }, + + '&:hover': { + color: radioButtonHoverColor, + borderColor: radioButtonHoverColor, + + '&::before': { + backgroundColor: radioButtonHoverColor, + }, + }, + + '&:active': { + color: radioButtonActiveColor, + borderColor: radioButtonActiveColor, + + '&::before': { + backgroundColor: radioButtonActiveColor, + }, + }, + }, + + [`${componentCls}-group-solid &-checked:not(${componentCls}-button-wrapper-disabled)`]: { + color: radioSolidCheckedColor, + background: radioCheckedColor, + borderColor: radioCheckedColor, + + '&:hover': { + color: radioSolidCheckedColor, + background: radioButtonHoverColor, + borderColor: radioButtonHoverColor, + }, + + '&:active': { + color: radioSolidCheckedColor, + background: radioButtonActiveColor, + borderColor: radioButtonActiveColor, + }, + }, + + '&-disabled': { + color: colorTextDisabled, + backgroundColor: colorBgContainerDisabled, + borderColor: colorBorder, + cursor: 'not-allowed', + + '&:first-child, &:hover': { + color: colorTextDisabled, + backgroundColor: colorBgContainerDisabled, + borderColor: colorBorder, + }, + }, + + [`&-disabled${componentCls}-button-wrapper-checked`]: { + color: radioDisabledButtonCheckedColor, + backgroundColor: radioDisabledButtonCheckedBg, + borderColor: colorBorder, + boxShadow: 'none', + }, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('Radio', token => { + const { + padding, + lineWidth, + controlItemBgActiveDisabled, + colorTextDisabled, + colorBgContainer, + fontSizeLG, + controlOutline, + colorPrimaryHover, + colorPrimaryActive, + colorText, + colorPrimary, + marginXS, + controlOutlineWidth, + colorTextLightSolid, + wireframe, + } = token; + + // Radio + const radioFocusShadow = `0 0 0 ${controlOutlineWidth}px ${controlOutline}`; + const radioButtonFocusShadow = radioFocusShadow; + + const radioSize = fontSizeLG; + const dotPadding = 4; // Fixed value + const radioDotDisabledSize = radioSize - dotPadding * 2; + const radioDotSize = wireframe ? radioDotDisabledSize : radioSize - (dotPadding + lineWidth) * 2; + const radioCheckedColor = colorPrimary; + + // Radio buttons + const radioButtonColor = colorText; + const radioButtonHoverColor = colorPrimaryHover; + const radioButtonActiveColor = colorPrimaryActive; + const radioButtonPaddingHorizontal = padding - lineWidth; + const radioDisabledButtonCheckedColor = colorTextDisabled; + const radioWrapperMarginRight = marginXS; + + const radioToken = mergeToken(token, { + radioFocusShadow, + radioButtonFocusShadow, + radioSize, + radioDotSize, + radioDotDisabledSize, + radioCheckedColor, + radioDotDisabledColor: colorTextDisabled, + radioSolidCheckedColor: colorTextLightSolid, + radioButtonBg: colorBgContainer, + radioButtonCheckedBg: colorBgContainer, + radioButtonColor, + radioButtonHoverColor, + radioButtonActiveColor, + radioButtonPaddingHorizontal, + radioDisabledButtonCheckedBg: controlItemBgActiveDisabled, + radioDisabledButtonCheckedColor, + radioWrapperMarginRight, + }); + + return [ + getGroupRadioStyle(radioToken), + getRadioBasicStyle(radioToken), + getRadioButtonStyle(radioToken), + ]; +}); diff --git a/components/radio/style/rtl.less b/components/radio/style/rtl.less deleted file mode 100644 index c3c367ac18..0000000000 --- a/components/radio/style/rtl.less +++ /dev/null @@ -1,61 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@radio-prefix-cls: ~'@{ant-prefix}-radio'; -@radio-group-prefix-cls: ~'@{radio-prefix-cls}-group'; -@radio-prefix-cls-button-wrapper: ~'@{radio-prefix-cls}-button-wrapper'; - -.@{radio-group-prefix-cls} { - &&-rtl { - direction: rtl; - } -} - -// 一般状态 -.@{radio-prefix-cls}-wrapper { - &&-rtl { - margin-right: 0; - margin-left: @radio-wrapper-margin-right; - direction: rtl; - } -} - -.@{radio-prefix-cls-button-wrapper} { - &&-rtl { - border-right-width: 0; - border-left-width: @border-width-base; - } - - &:not(:first-child) { - &::before { - .@{radio-prefix-cls-button-wrapper}.@{radio-prefix-cls-button-wrapper}-rtl& { - right: -1px; - left: 0; - } - } - } - - &:first-child { - .@{radio-prefix-cls-button-wrapper}.@{radio-prefix-cls-button-wrapper}-rtl& { - border-right: @border-width-base @border-style-base @border-color-base; - border-radius: 0 @border-radius-base @border-radius-base 0; - } - .@{radio-prefix-cls-button-wrapper}-checked:not([class*=~"' @{radio-prefix-cls}-button-wrapper-disabled'"])& { - border-right-color: @radio-button-hover-color; - } - } - - &:last-child { - .@{radio-prefix-cls-button-wrapper}.@{radio-prefix-cls-button-wrapper}-rtl& { - border-radius: @border-radius-base 0 0 @border-radius-base; - } - } - - &-disabled { - &:first-child { - .@{radio-prefix-cls-button-wrapper}.@{radio-prefix-cls-button-wrapper}-rtl& { - border-right-color: @border-color-base; - } - } - } -} diff --git a/components/style.ts b/components/style.ts index 72d08903a9..96f4195546 100644 --- a/components/style.ts +++ b/components/style.ts @@ -1,6 +1,6 @@ // import './button/style'; // import './icon/style'; -import './radio/style'; +// import './radio/style'; // import './checkbox/style'; // import './grid/style'; // import './tag/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 90ba2312ce..d1b9505f60 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -27,7 +27,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style'; import type { ComponentToken as PopoverComponentToken } from '../../popover/style'; import type { ComponentToken as ProgressComponentToken } from '../../progress/style'; -// import type { ComponentToken as RadioComponentToken } from '../../radio/style'; +import type { ComponentToken as RadioComponentToken } from '../../radio/style'; import type { ComponentToken as RateComponentToken } from '../../rate/style'; import type { ComponentToken as ResultComponentToken } from '../../result/style'; // import type { ComponentToken as SegmentedComponentToken } from '../../segmented/style'; @@ -87,7 +87,7 @@ export interface ComponentTokenMap { Popover?: PopoverComponentToken; Popconfirm?: PopconfirmComponentToken; Rate?: RateComponentToken; - // Radio?: RadioComponentToken; + Radio?: RadioComponentToken; Result?: ResultComponentToken; Segmented?: SegmentedComponentToken; Select?: SelectComponentToken; From 01acc478e28f678872c81b912551253e6458622b Mon Sep 17 00:00:00 2001 From: shifeng1993 Date: Tue, 21 Feb 2023 01:05:43 +0800 Subject: [PATCH 2/2] fix attrs --- components/radio/Group.tsx | 3 ++- components/radio/Radio.tsx | 6 ++++-- components/radio/RadioButton.tsx | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/radio/Group.tsx b/components/radio/Group.tsx index 2c50bf7324..87774bb279 100644 --- a/components/radio/Group.tsx +++ b/components/radio/Group.tsx @@ -44,6 +44,7 @@ export type RadioGroupProps = Partial +
{children}
, ); diff --git a/components/radio/Radio.tsx b/components/radio/Radio.tsx index b6987c46ea..d081a15d01 100644 --- a/components/radio/Radio.tsx +++ b/components/radio/Radio.tsx @@ -36,8 +36,9 @@ export type RadioProps = Partial> export default defineComponent({ compatConfig: { MODE: 3 }, name: 'ARadio', + inheritAttrs: false, props: radioProps(), - setup(props, { emit, expose, slots }) { + setup(props, { emit, expose, slots, attrs }) { const formItemContext = useInjectFormItemContext(); const formItemInputContext = FormItemInputContext.useInject(); const radioOptionTypeContext = useInjectRadioOptionTypeContext(); @@ -105,11 +106,12 @@ export default defineComponent({ [`${prefixCls.value}-wrapper-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-wrapper-in-form-item`]: formItemInputContext.isFormItemInput, }, + attrs.class, hashId.value, ); return wrapSSR( -