From 77bef2a548bb0e5fe2aab4c69e2c2aea1788f7cc Mon Sep 17 00:00:00 2001 From: aibayanyu Date: Fri, 17 Mar 2023 22:38:25 +0800 Subject: [PATCH] feat: add button --- components/button/button.tsx | 38 +- components/button/style/group.ts | 80 ++++ components/button/style/index.ts | 503 +++++++++++++++++++++++ components/theme/interface/components.ts | 6 +- 4 files changed, 620 insertions(+), 7 deletions(-) create mode 100644 components/button/style/group.ts create mode 100644 components/button/style/index.ts diff --git a/components/button/button.tsx b/components/button/button.tsx index 2ee6fa3..626328e 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -1,11 +1,41 @@ -import { defineComponent } from 'vue' +import { computed, defineComponent } from 'vue' +import { useProviderConfigState } from '../config-provider/context' +import useStyle from './style' const Button = defineComponent({ name: 'AButton', - props: {}, - setup(props, { slots }) { + inheritAttrs: false, + props: { + prefixCls: { + type: String + }, + type: { + type: String, + default: 'default' + } + }, + setup(props, { slots, attrs }) { + const { getPrefixCls } = useProviderConfigState() + const prefixCls = computed(() => getPrefixCls('btn', props.prefixCls)) + const [wrapSSR, hashId] = useStyle(prefixCls) + + const cls = computed(() => { + return { + [prefixCls.value]: true, + [`${prefixCls.value}-${props.type}`]: !!props.type, + [hashId.value]: true + } + }) + return () => { - return + return wrapSSR( + + ) } } }) diff --git a/components/button/style/group.ts b/components/button/style/group.ts new file mode 100644 index 0000000..eccee25 --- /dev/null +++ b/components/button/style/group.ts @@ -0,0 +1,80 @@ +import type { GenerateStyle } from '../../theme/internal' +import type { ButtonToken } from '.' + +const genButtonBorderStyle = (buttonTypeCls: string, borderColor: string) => ({ + // Border + [`> span, > ${buttonTypeCls}`]: { + '&:not(:last-child)': { + [`&, & > ${buttonTypeCls}`]: { + '&:not(:disabled)': { + borderInlineEndColor: borderColor + } + } + }, + + '&:not(:first-child)': { + [`&, & > ${buttonTypeCls}`]: { + '&:not(:disabled)': { + borderInlineStartColor: borderColor + } + } + } + } +}) + +const genGroupStyle: GenerateStyle = token => { + const { componentCls, fontSize, lineWidth, colorPrimaryHover, colorErrorHover } = token + + return { + [`${componentCls}-group`]: [ + { + position: 'relative', + display: 'inline-flex', + + // Border + [`> span, > ${componentCls}`]: { + '&:not(:last-child)': { + [`&, & > ${componentCls}`]: { + borderStartEndRadius: 0, + borderEndEndRadius: 0 + } + }, + + '&:not(:first-child)': { + marginInlineStart: -lineWidth, + + [`&, & > ${componentCls}`]: { + borderStartStartRadius: 0, + borderEndStartRadius: 0 + } + } + }, + + [componentCls]: { + position: 'relative', + zIndex: 1, + + [`&:hover, + &:focus, + &:active`]: { + zIndex: 2 + }, + + '&[disabled]': { + zIndex: 0 + } + }, + + [`${componentCls}-icon-only`]: { + fontSize + } + }, + + // Border Color + genButtonBorderStyle(`${componentCls}-primary`, colorPrimaryHover), + genButtonBorderStyle(`${componentCls}-danger`, colorErrorHover) + ] + } +} + +export default genGroupStyle diff --git a/components/button/style/index.ts b/components/button/style/index.ts new file mode 100644 index 0000000..33d4121 --- /dev/null +++ b/components/button/style/index.ts @@ -0,0 +1,503 @@ +import type { CSSInterpolation, CSSObject } from '@antd-tiny-vue/cssinjs' +import type { FullToken, GenerateStyle } from '../../theme/internal' +import { genComponentStyleHook, mergeToken } from '../../theme/internal' +import { genFocusStyle } from '../../style' +import { genCompactItemStyle } from '../../style/compact-item' +import { genCompactItemVerticalStyle } from '../../style/compact-item-vertical' +import genGroupStyle from './group' + +/** Component only token. Which will handle additional calculation of alias token */ +export interface ComponentToken {} + +export interface ButtonToken extends FullToken<'Button'> { + // FIXME: should be removed + colorOutlineDefault: string + buttonPaddingHorizontal: number +} + +// ============================== Shared ============================== +const genSharedButtonStyle: GenerateStyle = (token): CSSObject => { + const { componentCls, iconCls } = token + + return { + [componentCls]: { + outline: 'none', + position: 'relative', + display: 'inline-block', + fontWeight: 400, + whiteSpace: 'nowrap', + textAlign: 'center', + backgroundImage: 'none', + backgroundColor: 'transparent', + border: `${token.lineWidth}px ${token.lineType} transparent`, + cursor: 'pointer', + transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`, + userSelect: 'none', + touchAction: 'manipulation', + lineHeight: token.lineHeight, + color: token.colorText, + + '> span': { + display: 'inline-block' + }, + + // Leave a space between icon and text. + [`> ${iconCls} + span, > span + ${iconCls}`]: { + marginInlineStart: token.marginXS + }, + + '> a': { + color: 'currentColor' + }, + + '&:not(:disabled)': { + ...genFocusStyle(token) + }, + + // make `btn-icon-only` not too narrow + [`&-icon-only${componentCls}-compact-item`]: { + flex: 'none' + }, + // Special styles for Primary Button + [`&-compact-item${componentCls}-primary`]: { + [`&:not([disabled]) + ${componentCls}-compact-item${componentCls}-primary:not([disabled])`]: { + position: 'relative', + + '&:before': { + position: 'absolute', + top: -token.lineWidth, + insetInlineStart: -token.lineWidth, + display: 'inline-block', + width: token.lineWidth, + height: `calc(100% + ${token.lineWidth * 2}px)`, + backgroundColor: token.colorPrimaryHover, + content: '""' + } + } + }, + // Special styles for Primary Button + '&-compact-vertical-item': { + [`&${componentCls}-primary`]: { + [`&:not([disabled]) + ${componentCls}-compact-vertical-item${componentCls}-primary:not([disabled])`]: { + position: 'relative', + + '&:before': { + position: 'absolute', + top: -token.lineWidth, + insetInlineStart: -token.lineWidth, + display: 'inline-block', + width: `calc(100% + ${token.lineWidth * 2}px)`, + height: token.lineWidth, + backgroundColor: token.colorPrimaryHover, + content: '""' + } + } + } + } + } + } +} + +const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({ + '&:not(:disabled)': { + '&:hover': hoverStyle, + '&:active': activeStyle + } +}) + +// ============================== Shape =============================== +const genCircleButtonStyle: GenerateStyle = token => ({ + minWidth: token.controlHeight, + paddingInlineStart: 0, + paddingInlineEnd: 0, + borderRadius: '50%' +}) + +const genRoundButtonStyle: GenerateStyle = token => ({ + borderRadius: token.controlHeight, + paddingInlineStart: token.controlHeight / 2, + paddingInlineEnd: token.controlHeight / 2 +}) + +// =============================== Type =============================== +const genDisabledStyle: GenerateStyle = token => ({ + cursor: 'not-allowed', + borderColor: token.colorBorder, + color: token.colorTextDisabled, + backgroundColor: token.colorBgContainerDisabled, + boxShadow: 'none' +}) + +const genGhostButtonStyle = ( + btnCls: string, + textColor: string | false, + borderColor: string | false, + textColorDisabled: string | false, + borderColorDisabled: string | false, + hoverStyle?: CSSObject, + activeStyle?: CSSObject +): CSSObject => ({ + [`&${btnCls}-background-ghost`]: { + color: textColor || undefined, + backgroundColor: 'transparent', + borderColor: borderColor || undefined, + boxShadow: 'none', + + ...genHoverActiveButtonStyle( + { + backgroundColor: 'transparent', + ...hoverStyle + }, + { + backgroundColor: 'transparent', + ...activeStyle + } + ), + + '&:disabled': { + cursor: 'not-allowed', + color: textColorDisabled || undefined, + borderColor: borderColorDisabled || undefined + } + } +}) + +const genSolidDisabledButtonStyle: GenerateStyle = token => ({ + '&:disabled': { + ...genDisabledStyle(token) + } +}) + +const genSolidButtonStyle: GenerateStyle = token => ({ + ...genSolidDisabledButtonStyle(token) +}) + +const genPureDisabledButtonStyle: GenerateStyle = token => ({ + '&:disabled': { + cursor: 'not-allowed', + color: token.colorTextDisabled + } +}) + +// Type: Default +const genDefaultButtonStyle: GenerateStyle = token => ({ + ...genSolidButtonStyle(token), + + backgroundColor: token.colorBgContainer, + borderColor: token.colorBorder, + + boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlTmpOutline}`, + + ...genHoverActiveButtonStyle( + { + color: token.colorPrimaryHover, + borderColor: token.colorPrimaryHover + }, + { + color: token.colorPrimaryActive, + borderColor: token.colorPrimaryActive + } + ), + + ...genGhostButtonStyle(token.componentCls, token.colorBgContainer, token.colorBgContainer, token.colorTextDisabled, token.colorBorder), + + [`&${token.componentCls}-dangerous`]: { + color: token.colorError, + borderColor: token.colorError, + + ...genHoverActiveButtonStyle( + { + color: token.colorErrorHover, + borderColor: token.colorErrorBorderHover + }, + { + color: token.colorErrorActive, + borderColor: token.colorErrorActive + } + ), + + ...genGhostButtonStyle(token.componentCls, token.colorError, token.colorError, token.colorTextDisabled, token.colorBorder), + ...genSolidDisabledButtonStyle(token) + } +}) + +// Type: Primary +const genPrimaryButtonStyle: GenerateStyle = token => ({ + ...genSolidButtonStyle(token), + + color: token.colorTextLightSolid, + backgroundColor: token.colorPrimary, + + boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.controlOutline}`, + + ...genHoverActiveButtonStyle( + { + color: token.colorTextLightSolid, + backgroundColor: token.colorPrimaryHover + }, + { + color: token.colorTextLightSolid, + backgroundColor: token.colorPrimaryActive + } + ), + + ...genGhostButtonStyle( + token.componentCls, + token.colorPrimary, + token.colorPrimary, + token.colorTextDisabled, + token.colorBorder, + { + color: token.colorPrimaryHover, + borderColor: token.colorPrimaryHover + }, + { + color: token.colorPrimaryActive, + borderColor: token.colorPrimaryActive + } + ), + + [`&${token.componentCls}-dangerous`]: { + backgroundColor: token.colorError, + boxShadow: `0 ${token.controlOutlineWidth}px 0 ${token.colorErrorOutline}`, + + ...genHoverActiveButtonStyle( + { + backgroundColor: token.colorErrorHover + }, + { + backgroundColor: token.colorErrorActive + } + ), + + ...genGhostButtonStyle( + token.componentCls, + token.colorError, + token.colorError, + token.colorTextDisabled, + token.colorBorder, + { + color: token.colorErrorHover, + borderColor: token.colorErrorHover + }, + { + color: token.colorErrorActive, + borderColor: token.colorErrorActive + } + ), + ...genSolidDisabledButtonStyle(token) + } +}) + +// Type: Dashed +const genDashedButtonStyle: GenerateStyle = token => ({ + ...genDefaultButtonStyle(token), + borderStyle: 'dashed' +}) + +// Type: Link +const genLinkButtonStyle: GenerateStyle = token => ({ + color: token.colorLink, + + ...genHoverActiveButtonStyle( + { + color: token.colorLinkHover + }, + { + color: token.colorLinkActive + } + ), + + ...genPureDisabledButtonStyle(token), + + [`&${token.componentCls}-dangerous`]: { + color: token.colorError, + + ...genHoverActiveButtonStyle( + { + color: token.colorErrorHover + }, + { + color: token.colorErrorActive + } + ), + + ...genPureDisabledButtonStyle(token) + } +}) + +// Type: Text +const genTextButtonStyle: GenerateStyle = token => ({ + ...genHoverActiveButtonStyle( + { + color: token.colorText, + backgroundColor: token.colorBgTextHover + }, + { + color: token.colorText, + backgroundColor: token.colorBgTextActive + } + ), + + ...genPureDisabledButtonStyle(token), + + [`&${token.componentCls}-dangerous`]: { + color: token.colorError, + + ...genPureDisabledButtonStyle(token), + ...genHoverActiveButtonStyle( + { + color: token.colorErrorHover, + backgroundColor: token.colorErrorBg + }, + { + color: token.colorErrorHover, + backgroundColor: token.colorErrorBg + } + ) + } +}) + +// Href and Disabled +const genDisabledButtonStyle: GenerateStyle = token => ({ + ...genDisabledStyle(token), + [`&${token.componentCls}:hover`]: { + ...genDisabledStyle(token) + } +}) + +const genTypeButtonStyle: GenerateStyle = token => { + const { componentCls } = token + + return { + [`${componentCls}-default`]: genDefaultButtonStyle(token), + [`${componentCls}-primary`]: genPrimaryButtonStyle(token), + [`${componentCls}-dashed`]: genDashedButtonStyle(token), + [`${componentCls}-link`]: genLinkButtonStyle(token), + [`${componentCls}-text`]: genTextButtonStyle(token), + [`${componentCls}-disabled`]: genDisabledButtonStyle(token) + } +} + +// =============================== Size =============================== +const genSizeButtonStyle = (token: ButtonToken, sizePrefixCls = ''): CSSInterpolation => { + const { componentCls, iconCls, controlHeight, fontSize, lineHeight, lineWidth, borderRadius, buttonPaddingHorizontal } = token + + const paddingVertical = Math.max(0, (controlHeight - fontSize * lineHeight) / 2 - lineWidth) + const paddingHorizontal = buttonPaddingHorizontal - lineWidth + + const iconOnlyCls = `${componentCls}-icon-only` + + return [ + // Size + { + [`${componentCls}${sizePrefixCls}`]: { + fontSize, + height: controlHeight, + padding: `${paddingVertical}px ${paddingHorizontal}px`, + borderRadius, + + [`&${iconOnlyCls}`]: { + width: controlHeight, + paddingInlineStart: 0, + paddingInlineEnd: 0, + [`&${componentCls}-round`]: { + width: 'auto' + }, + '> span': { + transform: 'scale(1.143)' // 14px -> 16px + } + }, + + // Loading + [`&${componentCls}-loading`]: { + opacity: token.opacityLoading, + cursor: 'default' + }, + + [`${componentCls}-loading-icon`]: { + transition: `width ${token.motionDurationSlow} ${token.motionEaseInOut}, opacity ${token.motionDurationSlow} ${token.motionEaseInOut}` + }, + + [`&:not(${iconOnlyCls}) ${componentCls}-loading-icon > ${iconCls}`]: { + marginInlineEnd: token.marginXS + } + } + }, + + // Shape - patch prefixCls again to override solid border radius style + { + [`${componentCls}${componentCls}-circle${sizePrefixCls}`]: genCircleButtonStyle(token) + }, + { + [`${componentCls}${componentCls}-round${sizePrefixCls}`]: genRoundButtonStyle(token) + } + ] +} + +const genSizeBaseButtonStyle: GenerateStyle = token => genSizeButtonStyle(token) + +const genSizeSmallButtonStyle: GenerateStyle = token => { + const smallToken = mergeToken(token, { + controlHeight: token.controlHeightSM, + padding: token.paddingXS, + buttonPaddingHorizontal: 8, // Fixed padding + borderRadius: token.borderRadiusSM + }) + + return genSizeButtonStyle(smallToken, `${token.componentCls}-sm`) +} + +const genSizeLargeButtonStyle: GenerateStyle = token => { + const largeToken = mergeToken(token, { + controlHeight: token.controlHeightLG, + fontSize: token.fontSizeLG, + borderRadius: token.borderRadiusLG + }) + + return genSizeButtonStyle(largeToken, `${token.componentCls}-lg`) +} + +const genBlockButtonStyle: GenerateStyle = token => { + const { componentCls } = token + return { + [componentCls]: { + [`&${componentCls}-block`]: { + width: '100%' + } + } + } +} + +// ============================== Export ============================== +export default genComponentStyleHook('Button', token => { + const { controlTmpOutline, paddingContentHorizontal } = token + + const buttonToken = mergeToken(token, { + colorOutlineDefault: controlTmpOutline, + buttonPaddingHorizontal: paddingContentHorizontal + }) + + return [ + // Shared + genSharedButtonStyle(buttonToken), + + // Size + genSizeSmallButtonStyle(buttonToken), + genSizeBaseButtonStyle(buttonToken), + genSizeLargeButtonStyle(buttonToken), + + // Block + genBlockButtonStyle(buttonToken), + + // Group (type, ghost, danger, disabled, loading) + genTypeButtonStyle(buttonToken), + + // Button Group + genGroupStyle(buttonToken), + + // Space Compact + genCompactItemStyle(token, { focus: false }), + genCompactItemVerticalStyle(token) + ] +}) diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index d85c468..02ea390 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -2,8 +2,8 @@ // import type { ComponentToken as AnchorComponentToken } from '../../anchor/style' // import type { ComponentToken as AvatarComponentToken } from '../../avatar/style' // import type { ComponentToken as BackTopComponentToken } from '../../back-top/style' -// import type { ComponentToken as ButtonComponentToken } from '../../button/style' -// import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style' +import type { ComponentToken as ButtonComponentToken } from '../../button/style' +// import type { ComponentToken as FloatButtonComponentToken } from '../../floats-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' @@ -57,7 +57,7 @@ export interface ComponentTokenMap { // Avatar?: AvatarComponentToken // BackTop?: BackTopComponentToken // Badge?: {} - // Button?: ButtonComponentToken + Button?: ButtonComponentToken // Breadcrumb?: {} // Card?: CardComponentToken // Carousel?: CarouselComponentToken