From 30ee2f7c55a87dc443a8489468ff5b5890d05d46 Mon Sep 17 00:00:00 2001 From: WarrenLee19 Date: Thu, 20 Oct 2022 19:23:50 +0800 Subject: [PATCH] =?UTF-8?q?fix:card=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/card/Card.tsx | 150 +++++++ components/card/Grid.tsx | 26 ++ components/card/index.tsx | 14 +- components/card/style/defaultConstant.scss | 48 +++ components/card/style/index.scss | 391 ++++++++++++++++++ components/config-provider/configProvider.tsx | 2 +- components/config-provider/index.tsx | 6 +- components/skeleton/index.tsx | 3 + site/views/card-view.tsx | 19 +- 9 files changed, 651 insertions(+), 8 deletions(-) create mode 100644 components/card/Card.tsx create mode 100644 components/card/Grid.tsx create mode 100644 components/card/style/defaultConstant.scss create mode 100644 components/card/style/index.scss create mode 100644 components/skeleton/index.tsx diff --git a/components/card/Card.tsx b/components/card/Card.tsx new file mode 100644 index 0000000..1fec221 --- /dev/null +++ b/components/card/Card.tsx @@ -0,0 +1,150 @@ +import * as React from 'react'; +import ClassName from 'classnames'; +import {ConfigContext} from '../config-provider' +import Skeleton from '../skeleton'; +import Grid from './Grid'; +import './style/index.scss' + +export type CardType = 'inner'; +export type CardSize = 'default' | 'small'; + +export interface CardProps extends Omit, 'title'> { + prefixCls?: string; // 样式前缀 + title?: React.ReactNode;//卡片标题 + extra?: React.ReactNode;//卡片右上角的操作区域 + bordered?: boolean;//是否有边框 + linerable?: boolean;//是否有线 + headStyle?: React.CSSProperties;//自定义标题区域样式 + hoverLinerable?: boolean;//是否可以hover + bodyStyle?: React.CSSProperties;//内容区域自定义样式 + style?: React.CSSProperties; + loading?: boolean;//当卡片内容还在加载中时,可以用 loading 展示一个占位 + hoverable?: boolean;//鼠标移过时可浮起 + children?: React.ReactNode; + id?: string; + className?: string; + size?: CardSize;//card 的尺寸 + type?: CardType;//卡片类型,可设置为 inner 或 不设置 + cover?: React.ReactNode;//卡片封面 + actions?: React.ReactNode[];//卡片操作组,位置在卡片底部 +} + +function Card({ + prefixCls: customizePrefixCls, + className, + extra, + headStyle = {}, + bodyStyle = {}, + title, + style, + loading, + bordered = true, + linerable, + size: customizeSize, + type, + cover, + actions, + children, + hoverable, + hoverLinerable, +}:CardProps) { + const { getPrefixCls,prefixCls, size} = React.useContext(ConfigContext) + const cardPrefixCls = getPrefixCls!(prefixCls, 'card', customizePrefixCls) + const mergedSize = customizeSize || size; + + const isContainGrid = () => { + let containGrid; + React.Children.forEach(children, (element: JSX.Element) => { + if (element && element.type && element.type === Grid) { + containGrid = true; + } + }); + return containGrid; + }; + + const cardHeadClassName = ClassName({ + [`${cardPrefixCls}-empty-line-head`]: !linerable, + [`${cardPrefixCls}-head`]: linerable, + }); + + const cardHeadLineClassName = ClassName({ + [`${cardPrefixCls}-empty-line-head-title`]: !linerable, + [`${cardPrefixCls}-head-title`]: linerable, + }); + + const classString = ClassName( + cardPrefixCls, + { + [`${cardPrefixCls}-loading`]: loading, + [`${cardPrefixCls}-bordered`]: bordered, + [`${cardPrefixCls}-non-bordered`]: !bordered, + [`${cardPrefixCls}-hoverable`]: hoverable, + [`${cardPrefixCls}-contain-grid`]: isContainGrid(), + [`${cardPrefixCls}-empty-line`]: !linerable, + [`${cardPrefixCls}-hoverLinerable`]: hoverLinerable, + [`${cardPrefixCls}-${mergedSize}`]: mergedSize, + [`${cardPrefixCls}-type-${type}`]: !!type, + }, + className, + ); + + let loadingBack:React.ReactNode; + loadingBack = ( + + {children} + + ) + + let head: React.ReactNode; + if (title || extra || linerable) { + head = ( +
+
+ {title &&
{title}
} + {extra &&
{extra}
} +
+
+ ); + } + + let content: React.ReactNode; + content = ( +
+ {loading ? loadingBack : children} +
+ ) + + const coverDom = cover ?
{cover}
: null; + + const getAction = (actions:React.ReactNode[])=>{ + return ( + actions.map( + (action,index)=>{ + return ( +
  • + + {action} + +
  • + ) + } + ) + ) + } + + const actionDom = + actions && actions.length ? ( + + ) : null; + + return ( +
    + {head} + {coverDom} + {content} + {actionDom} +
    + ); +} + +export default Card; diff --git a/components/card/Grid.tsx b/components/card/Grid.tsx new file mode 100644 index 0000000..cb5e60e --- /dev/null +++ b/components/card/Grid.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigConsumer } from '../config-provider'; + +export interface CardGridProps { + prefixCls?: string; + className?: string; + hoverable?: boolean; + style?: React.CSSProperties; +} + +const Grid: React.FC = ({ prefixCls, className, hoverable = true, ...props }) => ( + + {({ getPrefixCls }: ConfigConsumerProps) => { + const cardPrefixCls = getPrefixCls!(prefixCls, 'card') + const classString = classNames(`${cardPrefixCls}-grid`, className, { + [`${cardPrefixCls}-grid-hoverable`]: hoverable, + }); + + return
    ; + }} + +); + +export default Grid; diff --git a/components/card/index.tsx b/components/card/index.tsx index cd2618d..3e6f4e8 100644 --- a/components/card/index.tsx +++ b/components/card/index.tsx @@ -1,3 +1,15 @@ -import { Card } from 'apusic-ui'; +import InternalCard from './Card'; +import Grid from './Grid'; + + +type InternalCardType = typeof InternalCard; + +export interface CardInterface extends InternalCardType { + Grid: typeof Grid; +} + +const Card = InternalCard as CardInterface; + +Card.Grid = Grid; export default Card; diff --git a/components/card/style/defaultConstant.scss b/components/card/style/defaultConstant.scss new file mode 100644 index 0000000..dbf1a1c --- /dev/null +++ b/components/card/style/defaultConstant.scss @@ -0,0 +1,48 @@ +$text-color : #333333; +// height rules +$height-base: 32px; +$height-lg: 40px; +$height-sm: 24px; +// Card +// --- + +$card-head-color: $text-color; +$card-head-background: transparent; +$card-font-size: 14px; +$card-head-font-size: $card-font-size; +$card-head-font-size-sm: 12px; +$card-padding: 16px; +$card-head-padding: $card-padding; +$card-head-empty-padding: 20px 0 16px 0; +$card-head-padding-sm: calc($card-head-padding / 2); +$card-head-height: 48px; +$card-head-height-nm: 40px; +$card-head-height-sm: 36px; +$card-inner-head-padding: 12px; +$card-padding-base: 24px; +$card-padding-content: 20px; +$card-padding-base-sm: calc($card-padding-base / 2); +$card-padding-base-nm: 10px; +$card-actions-background: #fff; +$card-actions-li-margin: 12px 0; +$card-skeleton-bg: #cfd8dc; +$card-background: #fff; +$card-shadow: 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); +$card-props-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.05); +$card-radius: 2px; +$card-head-tabs-margin-bottom: -17px; +$card-head-extra-color: #4578F8; +$card-hoverable-hover-border: transparent; +$line-height-card: 2.8572; +$card-action-icon-size: 16px; +$gradient-min: fade($card-skeleton-bg, 20%); +$gradient-max: fade($card-skeleton-bg, 40%); + +//border +$border-color-base: #D8D8D8; // base border outline a component +$border-color-split: #EBEBEB; // split border inside a component +$border-color-inverse: #fff; +$border-width-base: 1px; // width of the border for a component +$border-style-base: solid; // style of a components border +$border-color-card: #d8d8d8; +$border-hover-color-card: #4578F8; diff --git a/components/card/style/index.scss b/components/card/style/index.scss new file mode 100644 index 0000000..f3a8793 --- /dev/null +++ b/components/card/style/index.scss @@ -0,0 +1,391 @@ +@use '../../style/themes/index' as theme; +@import './defaultConstant'; +$card-prefix-cls: '#{theme.$prefix}-card'; +.clearfix { +// https://github.com/ant-design/ant-design/issues/21301#issuecomment-583955229 +&::before { + display: table; + content: ''; +} + +&::after { + // https://github.com/ant-design/ant-design/issues/21864 + display: table; + clear: both; + content: ''; +} +} +.#{$card-prefix-cls}{ + 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-card; + } + &-non-bordered { + box-shadow: 0px 6px 12px 8px rgba(0,0,0,0.03), 0px 4px 8px 0px rgba(0,0,0,0.05), 0px 2px 4px -8px rgba(0,0,0,0.08); + } + + &-shadow { + box-shadow: $card-props-shadow; + } + + &-hoverLinerable { + cursor: pointer; + + &:hover { + border: $border-width-base $border-style-base $border-hover-color-card; + .#{$card-prefix-cls}-actions > li { + color: $border-hover-color-card; + } + } + } + + &-linerable { + border-bottom: $border-width-base $border-style-base $border-color-card; + } + + &-head { + min-height: $card-head-height-nm; + margin-bottom: -1px; // Fix card grid overflow bug: https://gw.alipayobjects.com/zos/rmsportal/XonYxBikwpgbqIQBeuhk.png + padding: 0 $card-padding-base-nm; + color: $card-head-color; + font-weight: 500; + font-size: 12px; + line-height: $card-head-height-nm; + background: $card-head-background; + border-bottom: $border-width-base $border-style-base $border-color-card; + @extend .clearfix; + + &-wrapper { + display: flex; + align-items: center; + } + + &-title { + display: inline-block; + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + > .#{theme.$prefix}-typography, + > .#{theme.$prefix}-typography-edit-content { + left: 0; + margin-top: 0; + margin-bottom: 0; + } + } + + .#{theme.$prefix}-tabs { + clear: both; + margin-bottom: $card-head-tabs-margin-bottom; + color: $text-color; + font-weight: normal; + font-size: $card-font-size; + + &-bar { + border-bottom: $border-width-base $border-style-base $border-color-split; + } + } + } + + &-empty-line-head { + min-height: $card-head-height; + margin-bottom: -1px; // Fix card grid overflow bug: https://gw.alipayobjects.com/zos/rmsportal/XonYxBikwpgbqIQBeuhk.png + padding: 0 20px; + color: $card-head-color; + font-weight: 600; + font-size: $card-head-font-size; + background: $card-head-background; + + &-wrapper { + display: flex; + align-items: center; + } + + &-title { + display: inline-block; + flex: 1; + padding: $card-head-empty-padding; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + > .#{theme.$prefix}-typography, + > .#{theme.$prefix}-typography-edit-content { + left: 0; + margin-top: 0; + margin-bottom: 0; + } + } + + .#{theme.$prefix}-tabs { + clear: both; + margin-bottom: $card-head-tabs-margin-bottom; + color: $text-color; + font-weight: normal; + font-size: $card-font-size; + + &-bar { + border-bottom: $border-width-base $border-style-base $border-color-split; + } + } + } + + &-extra { + float: right; + // https://stackoverflow.com/a/22429853/3040605 + margin-left: auto; + color: rgba(0, 0, 0, 0.45); + font-weight: normal; + font-size: $card-font-size; + + .#{$card-prefix-cls}-rtl & { + margin-right: auto; + margin-left: 0; + } + } + + &-body { + padding: $card-padding-content; + color: #666666; + line-height: 17px; + @extend .clearfix; + } + + &-empty-line{ + .#{$card-prefix-cls}-body{ + padding: 0 $card-padding-content $card-padding-content; + } + } + + &-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-card, 0 1px 0 0 $border-color-card, + 1px 1px 0 0 $border-color-card, 1px 0 0 0 $border-color-card inset, + 0 1px 0 0 $border-color-card 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-card; + @extend .clearfix; + + & > li { + float: left; + height: $height-lg; + text-align: center; + + .#{$card-prefix-cls}-rtl & { + float: right; + } + + > span { + position: relative; + display: block; + min-width: 32px; + font-size: $card-font-size; + line-height: $line-height-card; + cursor: pointer; + + &:hover { + color: $border-hover-color-card; + transition: color 0.3s; + } + + a:not(.#{theme.$prefix}-btn), + > .action { + display: inline-block; + width: 100%; + color: $text-color; + line-height: 22px; + transition: color 0.3s; + + &:hover { + color: $border-hover-color-card; + } + } + + > .action { + font-size: $card-action-icon-size; + line-height: 22px; + } + } + + &:not(:last-child) { + border-right: $border-width-base $border-style-base $border-color-card; + + .#{$card-prefix-cls}-rtl & { + border-right: none; + border-left: $border-width-base $border-style-base $border-color-card; + } + } + } + } + + &-type-inner &-head { + padding: 0 $card-padding-base; + background: hsl(0,0,98%); + + &-title { + padding: $card-inner-head-padding 0; + font-size: $card-font-size; + } + } + + &-type-inner &-body { + padding: $card-padding $card-padding-base; + } + + &-type-inner &-extra { + padding: $card-inner-head-padding + 1.5px 0; + } + + &-meta { + margin: -4px 0; + @extend .clearfix; + + &-avatar { + float: left; + padding-right: $card-padding; + + .#{$card-prefix-cls}-rtl & { + float: right; + padding-right: 0; + padding-left: $card-padding; + } + } + + &-detail { + overflow: hidden; + > div:not(:last-child) { + margin-bottom: 8px; + } + } + + &-title { + overflow: hidden; + color: $card-head-color; + font-weight: 500; + font-size: $card-font-size; + white-space: nowrap; + text-overflow: ellipsis; + } + + &-description { + color: $text-color; + } + } + + &-loading { + overflow: hidden; + } + + &-loading &-body { + user-select: none; + } + + &-loading-content { + p { + margin: 0; + } + } + + &-loading-block { + height: $card-font-size; + 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; + } + +} +.#{$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; + line-height: 25px; + + > .#{$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/config-provider/configProvider.tsx b/components/config-provider/configProvider.tsx index 31e5b4d..c78884f 100644 --- a/components/config-provider/configProvider.tsx +++ b/components/config-provider/configProvider.tsx @@ -7,7 +7,7 @@ export const defaultPrefixCls = 'au'; export const defaultIconPrefixCls = 'au-icon'; /* 样式前缀定义 END */ -interface ConfigConsumerProps { +export interface ConfigConsumerProps { locale: string; renderEmpty: Function; prefixCls: string; diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 4e00037..72cd5b2 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -1,3 +1,7 @@ -import { ConfigProvider } from './configProvider'; +import { ConfigProvider,ConfigContext,ConfigConsumerProps,ConfigConsumer } from './configProvider'; export default ConfigProvider; + +export {ConfigContext,ConfigConsumer} + +export type {ConfigConsumerProps} diff --git a/components/skeleton/index.tsx b/components/skeleton/index.tsx new file mode 100644 index 0000000..6e61947 --- /dev/null +++ b/components/skeleton/index.tsx @@ -0,0 +1,3 @@ +import {Skeleton} from 'apusic-ui'; + +export default Skeleton; diff --git a/site/views/card-view.tsx b/site/views/card-view.tsx index b446b7e..eba5dae 100644 --- a/site/views/card-view.tsx +++ b/site/views/card-view.tsx @@ -1,18 +1,27 @@ import { Card } from '../../components'; export default function CardView() { + const gridStyle: React.CSSProperties = { + width: '25%', + textAlign: 'center', + }; + return (
    - More} style={{ width: 300 }}> -

    Card content

    -

    Card content

    -

    Card content

    + More} style={{ width: 300 }} bordered={false}> + + Content + + Content + Content + Content - More} style={{ width: 300 }}> + More} style={{ width: 300 }} linerable={true} hoverLinerable={true}>

    Card content

    Card content

    Card content

    +
    ); } -- Gitee