diff --git a/components/card/Card.tsx b/components/card/Card.tsx index 41f4fd385..8b2655029 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -9,6 +9,7 @@ import type { SizeType } from '../config-provider'; import isPlainObject from 'lodash-es/isPlainObject'; import useConfigInject from '../config-provider/hooks/useConfigInject'; import devWarning from '../vc-util/devWarning'; +import useStyle from './style'; export interface CardTabListType { key: string; tab: any; @@ -51,10 +52,12 @@ export type CardProps = Partial>>; const Card = defineComponent({ compatConfig: { MODE: 3 }, name: 'ACard', + inheritAttrs: false, props: cardProps(), slots: ['title', 'extra', 'tabBarExtraContent', 'actions', 'cover', 'customTab'], - setup(props, { slots }) { + setup(props, { slots, attrs }) { const { prefixCls, direction, size } = useConfigInject('card', props); + const [wrapSSR, hashId] = useStyle(prefixCls); const getAction = (actions: VNodeTypes[]) => { const actionList = actions.map((action, index) => (isVNode(action) && !isEmptyElement(action)) || !isVNode(action) ? ( @@ -99,6 +102,7 @@ const Card = defineComponent({ const pre = prefixCls.value; const classString = { [`${pre}`]: true, + [hashId.value]: true, [`${pre}-loading`]: loading, [`${pre}-bordered`]: bordered, [`${pre}-hoverable`]: !!hoverable, @@ -190,13 +194,13 @@ const Card = defineComponent({ const actionDom = actions && actions.length ? : null; - return ( -
+ return wrapSSR( +
{head} {coverDom} {children && children.length ? body : null} {actionDom} -
+
, ); }; }, diff --git a/components/card/style/index.less b/components/card/style/index.less deleted file mode 100644 index 7c02be1f4..000000000 --- a/components/card/style/index.less +++ /dev/null @@ -1,308 +0,0 @@ -@import '../../style/themes/index'; -@import '../../style/mixins/index'; - -@card-prefix-cls: ~'@{ant-prefix}-card'; -@card-hoverable-hover-border: transparent; -@card-action-icon-size: 16px; - -@gradient-min: fade(@card-skeleton-bg, 20%); -@gradient-max: fade(@card-skeleton-bg, 40%); - -.@{card-prefix-cls} { - .reset-component(); - - position: relative; - background: @card-background; - border-radius: @card-radius; - - &-rtl { - direction: rtl; - } - - &-hoverable { - cursor: pointer; - transition: box-shadow 0.3s, border-color 0.3s; - - &:hover { - border-color: @card-hoverable-hover-border; - box-shadow: @card-shadow; - } - } - - &-bordered { - border: @border-width-base @border-style-base @border-color-split; - } - - &-head { - min-height: @card-head-height; - margin-bottom: -1px; // Fix card grid overflow bug: https://gw.alipayobjects.com/zos/rmsportal/XonYxBikwpgbqIQBeuhk.png - padding: 0 @card-padding-base; - color: @card-head-color; - font-weight: 500; - font-size: @card-head-font-size; - background: @card-head-background; - border-bottom: @border-width-base @border-style-base @border-color-split; - border-radius: @card-radius @card-radius 0 0; - .clearfix(); - - &-wrapper { - display: flex; - align-items: center; - } - - &-title { - display: inline-block; - flex: 1; - padding: @card-head-padding 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - > .@{ant-prefix}-typography, - > .@{ant-prefix}-typography-edit-content { - left: 0; - margin-top: 0; - margin-bottom: 0; - } - } - - .@{ant-prefix}-tabs-top { - clear: both; - margin-bottom: @card-head-tabs-margin-bottom; - color: @text-color; - font-weight: normal; - font-size: @font-size-base; - - &-bar { - border-bottom: @border-width-base @border-style-base @border-color-split; - } - } - } - - &-extra { - float: right; - // https://stackoverflow.com/a/22429853/3040605 - margin-left: auto; - padding: @card-head-padding 0; - color: @card-head-extra-color; - font-weight: normal; - font-size: @font-size-base; - - .@{card-prefix-cls}-rtl & { - margin-right: auto; - margin-left: 0; - } - } - - &-body { - padding: @card-padding-base; - .clearfix(); - } - - &-contain-grid:not(&-loading) &-body { - margin: -1px 0 0 -1px; - padding: 0; - } - - &-grid { - float: left; - width: 33.33%; - padding: @card-padding-base; - border: 0; - border-radius: 0; - box-shadow: 1px 0 0 0 @border-color-split, 0 1px 0 0 @border-color-split, - 1px 1px 0 0 @border-color-split, 1px 0 0 0 @border-color-split inset, - 0 1px 0 0 @border-color-split inset; - transition: all 0.3s; - - .@{card-prefix-cls}-rtl & { - float: right; - } - - &-hoverable { - &:hover { - position: relative; - z-index: 1; - box-shadow: @card-shadow; - } - } - } - - &-contain-tabs > &-head &-head-title { - min-height: @card-head-height - @card-head-padding; - padding-bottom: 0; - } - - &-contain-tabs > &-head &-extra { - padding-bottom: 0; - } - - &-bordered &-cover { - margin-top: -1px; - margin-right: -1px; - margin-left: -1px; - } - - &-cover { - > * { - display: block; - width: 100%; - } - - img { - border-radius: @card-radius @card-radius 0 0; - } - } - - &-actions { - margin: 0; - padding: 0; - list-style: none; - background: @card-actions-background; - border-top: @border-width-base @border-style-base @border-color-split; - .clearfix(); - - & > li { - float: left; - margin: @card-actions-li-margin; - color: @text-color-secondary; - text-align: center; - - .@{card-prefix-cls}-rtl & { - float: right; - } - - > span { - position: relative; - display: block; - min-width: 32px; - font-size: @font-size-base; - line-height: @line-height-base; - cursor: pointer; - - &:hover { - color: @primary-color; - transition: color 0.3s; - } - - a:not(.@{ant-prefix}-btn), - > .@{iconfont-css-prefix} { - display: inline-block; - width: 100%; - color: @text-color-secondary; - line-height: 22px; - transition: color 0.3s; - - &:hover { - color: @primary-color; - } - } - - > .@{iconfont-css-prefix} { - font-size: @card-action-icon-size; - line-height: 22px; - } - } - - &:not(:last-child) { - border-right: @border-width-base @border-style-base @border-color-split; - - .@{card-prefix-cls}-rtl & { - border-right: none; - border-left: @border-width-base @border-style-base @border-color-split; - } - } - } - } - - &-type-inner &-head { - padding: 0 @card-padding-base; - background: @background-color-light; - - &-title { - padding: @card-inner-head-padding 0; - font-size: @font-size-base; - } - } - - &-type-inner &-body { - padding: 16px @card-padding-base; - } - - &-type-inner &-extra { - padding: @card-inner-head-padding + 1.5px 0; - } - - &-meta { - margin: -4px 0; - .clearfix(); - - &-avatar { - float: left; - padding-right: 16px; - - .@{card-prefix-cls}-rtl & { - float: right; - padding-right: 0; - padding-left: 16px; - } - } - - &-detail { - overflow: hidden; - - > div:not(:last-child) { - margin-bottom: @margin-xs; - } - } - - &-title { - overflow: hidden; - color: @card-head-color; - font-weight: 500; - font-size: @font-size-lg; - white-space: nowrap; - text-overflow: ellipsis; - } - - &-description { - color: @text-color-secondary; - } - } - - &-loading { - overflow: hidden; - } - - &-loading &-body { - user-select: none; - } - - &-loading-content { - p { - margin: 0; - } - } - - &-loading-block { - height: 14px; - margin: 4px 0; - background: linear-gradient(90deg, @gradient-min, @gradient-max, @gradient-min); - background-size: 600% 600%; - border-radius: @card-radius; - animation: card-loading 1.4s ease infinite; - } -} - -@keyframes card-loading { - 0%, - 100% { - background-position: 0 50%; - } - - 50% { - background-position: 100% 50%; - } -} - -@import './size'; diff --git a/components/card/style/index.tsx b/components/card/style/index.tsx index 1ab67bfcc..5fa85c450 100644 --- a/components/card/style/index.tsx +++ b/components/card/style/index.tsx @@ -1,7 +1,370 @@ -import '../../style/index.less'; -import './index.less'; +import type { CSSObject } from '../../_util/cssinjs'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { resetComponent, clearFix } from '../../_style'; -// style dependencies -import '../../tabs/style'; -import '../../row/style'; -import '../../col/style'; +/** Component only token. Which will handle additional calculation of alias token */ +export interface ComponentToken {} +interface CardToken extends FullToken<'Card'> { + cardHeadFontSize: string; + CardHeadPadding: string; + cardPaddingBase: string; + cardPaddingBaseSm: string; + cardHeadHeightSm: string; + cardHeadPaddingSm: string; + cardActionsLiMargin: string; + cardHeadTabsMarginBottom: string; + cardHeadHeight: string; + cardShadow: string; + cardHeadFontSizeSm: string; + cardHeadColor: string; + gradientMin: string; + gradientMax: string; + cardInnerHeadPadding: string; + transitionTime: string; +} +// ============================== Shared ============================== +export const genCardSmallStyle = (token: CardToken): CSSObject => { + + const { + cardPaddingBaseSm, + cardHeadHeightSm, + cardHeadPaddingSm, + cardHeadFontSizeSm, + componentCls, + } = token; + + return { + [`> ${componentCls}-head`]: { + minHeight: `${cardHeadHeightSm}`, + padding: `0 ${cardPaddingBaseSm}`, + fontSize: `${cardHeadFontSizeSm}px`, + [`> ${componentCls}-head-wrapper`]: { + [`> ${componentCls}-head-title`]: { + padding: `${cardHeadPaddingSm} 0`, + }, + [`> ${componentCls}-head-extra`]: { + padding: `${cardHeadPaddingSm} 0`, + fontSize: `${cardHeadFontSizeSm}px`, + }, + }, + }, + [`> ${componentCls}-body`]: { + padding: `${cardPaddingBaseSm}`, + }, + }; +}; + +const genSharedCardStyle: GenerateStyle = (token): CSSObject => { + const { + cardHeadFontSize, + CardHeadPadding, + cardPaddingBase, + cardHeadPaddingSm, + cardActionsLiMargin, + cardHeadTabsMarginBottom, + cardHeadHeight, + componentCls, + cardHeadColor, + cardShadow, + transitionTime, + antCls + } = token; + return { + [componentCls]: { + ...resetComponent(token), + position: `relative`, + background: `${token.colorBgBase}`, + borderRadius: `${token.borderRadiusXS}px`, + '&-bordered': { + border: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + }, + [`&${componentCls}-small`]: { + ...genCardSmallStyle(token), + }, + [`&-rtl`]: { + direction: 'rtl', + }, + [`&-hoverable`]: { + cursor: 'pointer', + transition: ` box-shadow ${transitionTime}, border-color ${transitionTime}`, + '&:hover': { + borderColor: `${token.colorBgBase}`, + boxShadow: `${cardShadow}`, + }, + }, + [`${componentCls}-head`]: { + minHeight: `${cardHeadHeight}`, + marginBottom: `-1px`, + overflow: 'hidden', + whiteSpace: 'nowrap', + color: `${cardHeadColor}`, + textOverflow: 'ellipsis', + padding: ` 0 ${token.cardPaddingBase} `, + fontWeight: 500, + fontSize: `${cardHeadFontSize}px`, + background: `transparent`, + borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + ...clearFix(), + '&-wrapper': { + display: 'flex', + alignItems: 'center', + }, + '&-title': { + display: 'inline-block', + flex: 1, + padding: `${CardHeadPadding} 0`, + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + [`> ${antCls}-typography ${antCls}-typography-edit-content `]: { + left: 0, + marginTop: 0, + marginBottom: 0, + }, + }, + + [`${antCls}-tabs-top`]: { + clear: 'both', + marginBottom: `${cardHeadTabsMarginBottom}`, + color: `${token.colorText}`, + fontWeight: 400, + fontSize: `${token.fontSize}`, + '&-bar': { + borderBottom: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + }, + }, + }, + [`${componentCls}-extra`]: { + float: 'right', + // https://stackoverflow.com/a/22429853/3040605 + marginLeft: 'auto', + padding: `${CardHeadPadding} 0`, + color: `${token.colorText}`, + fontWeight: 400, + fontSize: `${token.fontSize}`, + [`${componentCls}-prefix-rtl &`]: { + marginRight: 'auto', + marginLeft: 0, + }, + }, + [`${componentCls}-body`]: { + padding: `${cardPaddingBase}`, + ...clearFix(), + }, + [`${componentCls}-contain-grid:not(&-loading) ${componentCls}-body`]: { + margin: ' -1px 0 0 -1px', + padding: 0, + }, + [`${componentCls}-grid`]: { + float: 'left', + width: '33.33%', + padding: `${cardPaddingBase}`, + border: 'none', + borderRadius: 0, + boxShadow: `1px 0 0 0 ${token.colorSplit}, 0 1px 0 0 ${token.colorSplit}, + 1px 1px 0 0 ${token.colorSplit}, 1px 0 0 0 ${token.colorSplit} inset, + 0 1px 0 0 ${token.colorSplit} inset`, + transition: `all ${transitionTime}`, + [`${componentCls}-cls-rtl &`]: { + float: 'right', + }, + + '&-hoverable': { + '&:hover': { + position: 'relative', + zIndex: `${token.zIndexBase}`, + boxShadow: `${cardShadow}`, + }, + }, + }, + [` ${componentCls}-contain-tabs > ${componentCls}-head ${componentCls}-head-title`]: { + minHeight: ` ${cardHeadHeight}- ${cardPaddingBase}`, + paddingBottom: 0, + }, + [` ${componentCls}-contain-tabs > ${componentCls}-head ${componentCls}-extra`]: { + paddingBottom: 0, + }, + [` ${componentCls}-bordered > ${componentCls}-cover `]: { + marginTop: '-1px', + marginRight: '-1px', + marginLeft: '-1px', + }, + [`${componentCls}-cover`]: { + ' > *': { + display: 'block', + width: '100%', + }, + + img: { + borderRadius: `${token.borderRadiusXS}px ${token.borderRadiusXS}px 0 0 `, + }, + }, + + [`${componentCls}-actions`]: { + margin: 0, + padding: 0, + listStyle: 'none', + background: `${token.colorBgBase}`, + borderTop: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + ...clearFix(), + + '& > li': { + float: 'left', + margin: `${cardActionsLiMargin} `, + color: `${token.colorTextSecondary}`, + textAlign: 'center', + [` ${componentCls}-rtl &`]: { + float: 'right', + }, + + ' > span': { + position: 'relative', + display: 'block', + minWidth: ' 32px', + fontSize: `${token.fontSize}px`, + lineHeight: `${token.lineHeight}`, + cursor: 'pointer', + '&:hover': { + color: ` ${token.colorPrimary}`, + transition: `color ${transitionTime}`, + }, + + [`a:not(${antCls}-btn), >${token.iconCls}-css-prefix}`]: { + display: ' inline-block', + width: '100%', + color: `${token.colorTextSecondary}`, + lineHeight: ' 22px', + transition: `color ${transitionTime}`, + + '&:hover': { + color: `${token.colorPrimary}`, + }, + }, + + [`>${token.iconCls}`]: { + fontSize: `${token.fontSizeIcon}`, + lineHeight: '22px', + }, + }, + + [`li:not(:last-child)`]: { + borderRight: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + [`${componentCls}-rtl &`]: { + borderRight: 'none', + borderLeft: `${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}`, + }, + }, + }, + }, + [`${componentCls}-type-inner ${componentCls}-head`]: { + padding: `0 ${cardPaddingBase}`, + background: `${token.colorBgBase}`, + ' &-title': { + padding: `${cardHeadPaddingSm} 0`, + fontSize: `${token.fontSize}px`, + }, + }, + [`${componentCls}-type-inner ${componentCls}-body`]: { + padding: `16px ${cardPaddingBase}`, + }, + ' &-type-inner &-extra': { + padding: `${token.cardInnerHeadPadding} + 1.5px 0`, + }, + [`${componentCls}-meta`]: { + margin: ' -4px 0', + ...clearFix(), + + '&-avatar': { + float: 'left', + paddingRight: '16px', + + [` ${componentCls}-rtl &`]: { + float: 'right', + paddingRight: 0, + paddingLeft: ' 16px', + }, + }, + + '&-detail ': { + overflow: 'hidden', + '> div:not(:last-child)': { + marginBottom: `${token['magenta-8']}`, + }, + }, + + '&-title': { + overflow: 'hidden', + color: `${cardHeadColor}`, + fontWeight: '500', + fontSize: `${token.fontSizeLG}px`, + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, + + ' &-description': { + color: `${token.colorTextSecondary}`, + }, + }, + [` ${componentCls}-loading`]: { + overflow: 'hidden', + }, + [` ${componentCls}-loading ${componentCls}-body`]: { + userSelect: 'none', + }, + + [` ${componentCls}-loading-content`]: { + p: { + margin: 0, + }, + }, + [` ${componentCls}-loading-block`]: { + height: '14px', + margin: ' 4px 0', + background: `linear-gradient(90deg,${token.gradientMin},${token.gradientMax}, ${token.gradientMin})`, + backgroundSize: '600% 600%', + borderRadius: `${token.borderRadiusXS}px`, + animationName: 'card-loading', + animationDuration: '1.4s', + animationTimingFunction: 'ease', + animationIterationCount: 'infinite', + }, + '@keyframes card-loading': { + '0%,100% ': { + backgroundPosition: '0 50%', + }, + '50%': { + backgroundPosition: '100% 50%', + }, + }, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook( + 'Card', + token => { + const cardToken = mergeToken(token, { + cardPaddingBase: '16px', + cardHeadHeight: ' 36px', + cardHeadFontSize: `${token.fontSizeHeading5}`, + cardHeadFontSizeSm: `${token.fontSize}`, + CardHeadPadding: '8.5px', + cardPaddingBaseSm: '12px', + cardHeadHeightSm: '30px', + cardHeadPaddingSm: '6px', + cardActionsLiMargin: '12px 0', + cardHeadTabsMarginBottom: '-17px', + cardShadow: `0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), + 0 5px 12px 4px rgba(0, 0, 0, 0.09)`, + cardHeadColor: 'rgba(0,0,0,.85)', + gradientMin: 'rgba(207,216,220,.2)', + gradientMax: 'rgba(207,216,220,.4)', + cardInnerHeadPadding: '12px', + transitionTime: '0.3s', + }); + return [genSharedCardStyle(cardToken)]; + }, + {}, +); diff --git a/components/card/style/size.less b/components/card/style/size.less deleted file mode 100644 index d78f464a7..000000000 --- a/components/card/style/size.less +++ /dev/null @@ -1,20 +0,0 @@ -.@{card-prefix-cls}-small { - > .@{card-prefix-cls}-head { - min-height: @card-head-height-sm; - padding: 0 @card-padding-base-sm; - font-size: @card-head-font-size-sm; - - > .@{card-prefix-cls}-head-wrapper { - > .@{card-prefix-cls}-head-title { - padding: @card-head-padding-sm 0; - } - > .@{card-prefix-cls}-extra { - padding: @card-head-padding-sm 0; - font-size: @card-head-font-size-sm; - } - } - } - > .@{card-prefix-cls}-body { - padding: @card-padding-base-sm; - } -} diff --git a/components/input/style/index.ts b/components/input/style/index.ts index edbcb16dd..41a0e718d 100644 --- a/components/input/style/index.ts +++ b/components/input/style/index.ts @@ -904,7 +904,7 @@ const genTextAreaStyle: GenerateStyle = token => { [`> ${componentCls}`]: { height: '100%', }, - + '&::after': { color: token.colorTextDescription, whiteSpace: 'nowrap', diff --git a/components/style.ts b/components/style.ts index 566d1da40..50dd6b1a0 100644 --- a/components/style.ts +++ b/components/style.ts @@ -17,7 +17,7 @@ import './tabs/style'; import './mentions/style'; // import './dropdown/style'; // import './divider/style'; -import './card/style'; +// import './card/style'; import './collapse/style'; import './carousel/style'; // import './notification/style'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index fd3707fbb..4063c7c9b 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -5,7 +5,7 @@ import type { ComponentToken as BackTopComponentToken } from '../../back-top/sty 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 CardComponentToken } from '../../card/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'; @@ -59,7 +59,7 @@ export interface ComponentTokenMap { Badge?: {}; Button?: ButtonComponentToken; Breadcrumb?: {}; - // Card?: CardComponentToken; + Card?: CardComponentToken; // Carousel?: CarouselComponentToken; // Cascader?: CascaderComponentToken; // Checkbox?: CheckboxComponentToken; diff --git a/components/theme/interface/presetColors.ts b/components/theme/interface/presetColors.ts index 2cff21512..b20aafbf6 100644 --- a/components/theme/interface/presetColors.ts +++ b/components/theme/interface/presetColors.ts @@ -14,7 +14,7 @@ export const PresetColors = [ 'gold', ] as const; -export type PresetColorKey = typeof PresetColors[number]; +export type PresetColorKey = (typeof PresetColors)[number]; export type PresetColorType = Record;