Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add classNames and styles prop for card #46811

Merged
merged 34 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f0908dd
feat: support classNames and styles for card
zh-lx Dec 5, 2023
4613fcb
feat: add lost info
zh-lx Dec 5, 2023
36720e4
docs: update context of card
zh-lx Dec 5, 2023
f24f427
feat: optimize classNames and styles code of card
zh-lx Dec 5, 2023
b564a59
test: update card test case
zh-lx Dec 5, 2023
8e4c7a4
feat: remove headWrapper
zh-lx Dec 5, 2023
b9b879d
test: correct test
zh-lx Dec 5, 2023
f17f1b4
feat: remove redundant snapshot
zh-lx Dec 5, 2023
ef30882
feat: update config provider for card
zh-lx Dec 6, 2023
87bac8d
feat: update classNames and styles of card
zh-lx Dec 6, 2023
39a477c
Merge branch 'master' into card-custom-classname
zh-lx Dec 6, 2023
fcade66
Merge branch 'master' into card-custom-classname
zh-lx Dec 6, 2023
d655308
Merge branch 'master' into card-custom-classname
zh-lx Dec 11, 2023
efb78f6
Merge branch 'master' into card-custom-classname
zh-lx Dec 11, 2023
93f006a
Merge branch 'master' into card-custom-classname
vagusX Dec 11, 2023
347a6f6
Merge branch 'master' into card-custom-classname
zh-lx Dec 12, 2023
11bc856
Merge branch 'master' into card-custom-classname
zh-lx Dec 13, 2023
f8167b5
Merge branch 'master' into card-custom-classname
zh-lx Dec 15, 2023
da84f0c
Merge branch 'master' into card-custom-classname
zh-lx Dec 20, 2023
dc467f0
Merge branch 'master' into card-custom-classname
zh-lx Dec 27, 2023
6b87992
Merge branch 'feature' into card-custom-classname
zh-lx Jan 5, 2024
819e92a
feat: add warning for headStyle and bodyStyle of card
zh-lx Jan 5, 2024
1fb2c68
Merge branch 'feature' into card-custom-classname
zh-lx Jan 5, 2024
bd88a35
test: replace bodyStyle to styles.body in demo of flex
zh-lx Jan 5, 2024
89aa377
Merge remote-tracking branch 'origin/card-custom-classname' into card…
zh-lx Jan 5, 2024
96e3165
docs: add jsDoc about deprecated for headStyle and bodyStyle
zh-lx Jan 5, 2024
641b2cc
snap: update table counts of card from 3 to 4
zh-lx Jan 5, 2024
eb66a69
Merge branch 'feature' into card-custom-classname
zh-lx Jan 16, 2024
1f668e0
Merge branch 'feature' into card-custom-classname
zh-lx Jan 16, 2024
0c647c0
Merge branch 'feature' into card-custom-classname
zh-lx Jan 17, 2024
b72462e
Merge branch 'feature' into card-custom-classname
zh-lx Jan 28, 2024
96f8483
docs: update version for styles and classnames of card
zh-lx Jan 29, 2024
3c1ebc8
Merge branch 'feature' into card-custom-classname
zh-lx Jan 29, 2024
492705f
Merge branch 'feature' into card-custom-classname
zh-lx Jan 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
97 changes: 88 additions & 9 deletions components/card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import type { Tab } from 'rc-tabs/lib/interface';
import omit from 'rc-util/lib/omit';

import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useSize from '../config-provider/hooks/useSize';
import Skeleton from '../skeleton';
Expand All @@ -26,7 +27,9 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 't
title?: React.ReactNode;
extra?: React.ReactNode;
bordered?: boolean;
/** @deprecated Please use `styles.header` instead */
headStyle?: React.CSSProperties;
/** @deprecated Please use `styles.body` instead */
bodyStyle?: React.CSSProperties;
style?: React.CSSProperties;
loading?: boolean;
Expand All @@ -45,12 +48,35 @@ export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 't
activeTabKey?: string;
defaultActiveTabKey?: string;
tabProps?: TabsProps;
classNames?: {
header?: string;
body?: string;
extra?: string;
title?: string;
actions?: string;
cover?: string;
};
styles?: {
header?: React.CSSProperties;
body?: React.CSSProperties;
extra?: React.CSSProperties;
title?: React.CSSProperties;
actions?: React.CSSProperties;
cover?: React.CSSProperties;
};
}

const ActionNode: React.FC<{ prefixCls: string; actions: React.ReactNode[] }> = (props) => {
const { prefixCls, actions = [] } = props;
type CardClassNamesModule = keyof Exclude<CardProps['classNames'], undefined>;
type CardStylesModule = keyof Exclude<CardProps['styles'], undefined>;

const ActionNode: React.FC<{
actionClasses: string;
actions: React.ReactNode[];
actionStyle: React.CSSProperties;
}> = (props) => {
const { actionClasses, actions = [], actionStyle } = props;
return (
<ul className={`${prefixCls}-actions`}>
<ul className={actionClasses} style={actionStyle}>
{actions.map<React.ReactNode>((action, index) => {
// Move this out since eslint not allow index key
// And eslint-disable makes conflict with rollup
Expand Down Expand Up @@ -89,15 +115,36 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
tabBarExtraContent,
hoverable,
tabProps = {},
classNames: customClassNames,
styles: customStyles,
...others
} = props;

const { getPrefixCls, direction, card } = React.useContext(ConfigContext);

// =================Warning===================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Card');
[
['headStyle', 'styles.header'],
['bodyStyle', 'styles.body'],
].forEach(([deprecatedName, newName]) => {
warning.deprecated(!(deprecatedName in props), deprecatedName, newName);
});
}

zh-lx marked this conversation as resolved.
Show resolved Hide resolved
const onTabChange = (key: string) => {
props.onTabChange?.(key);
};

const moduleClass = (moduleName: CardClassNamesModule) =>
classNames(card?.classNames?.[moduleName], customClassNames?.[moduleName]);

const moduleStyle = (moduleName: CardStylesModule) => ({
...card?.styles?.[moduleName],
...customStyles?.[moduleName],
});

const isContainGrid = React.useMemo<boolean>(() => {
let containGrid = false;
React.Children.forEach(children, (element: JSX.Element) => {
Expand Down Expand Up @@ -139,25 +186,57 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>((props, ref) => {
/>
) : null;
if (title || extra || tabs) {
const headClasses = classNames(`${prefixCls}-head`, moduleClass('header'));
const titleClasses = classNames(`${prefixCls}-head-title`, moduleClass('title'));
const extraClasses = classNames(`${prefixCls}-extra`, moduleClass('extra'));
const mergedHeadStyle: React.CSSProperties = {
...headStyle,
...moduleStyle('header'),
};
head = (
<div className={`${prefixCls}-head`} style={headStyle}>
<div className={headClasses} style={mergedHeadStyle}>
<div className={`${prefixCls}-head-wrapper`}>
{title && <div className={`${prefixCls}-head-title`}>{title}</div>}
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
{title && (
<div className={titleClasses} style={moduleStyle('title')}>
{title}
</div>
)}
{extra && (
<div className={extraClasses} style={moduleStyle('extra')}>
{extra}
</div>
)}
</div>
{tabs}
</div>
);
}
const coverDom = cover ? <div className={`${prefixCls}-cover`}>{cover}</div> : null;
const coverClasses = classNames(`${prefixCls}-cover`, moduleClass('cover'));
const coverDom = cover ? (
<div className={coverClasses} style={moduleStyle('cover')}>
{cover}
</div>
) : null;
const bodyClasses = classNames(`${prefixCls}-body`, moduleClass('body'));
const mergedBodyStyle: React.CSSProperties = {
...bodyStyle,
...moduleStyle('body'),
};
const body = (
<div className={`${prefixCls}-body`} style={bodyStyle}>
<div className={bodyClasses} style={mergedBodyStyle}>
{loading ? loadingBlock : children}
</div>
);

const actionClasses = classNames(`${prefixCls}-actions`, moduleClass('actions'));
const actionDom =
actions && actions.length ? <ActionNode prefixCls={prefixCls} actions={actions} /> : null;
actions && actions.length ? (
<ActionNode
actionClasses={actionClasses}
actionStyle={moduleStyle('actions')}
actions={actions}
/>
) : null;

const divProps = omit(others, ['onTabChange']);

Expand Down
59 changes: 59 additions & 0 deletions components/card/__tests__/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,62 @@ exports[`Card title should be vertically aligned 1`] = `
</div>
</div>
`;

exports[`Card should support custom className 1`] = `
<div>
<div
class="ant-card ant-card-bordered"
>
<div
class="ant-card-head custom-head"
>
<div
class="ant-card-head-wrapper"
>
<div
class="ant-card-head-title"
>
Card title
</div>
</div>
</div>
<div
class="ant-card-body"
>
<p>
Card content
</p>
</div>
</div>
</div>
`;

exports[`Card should support custom styles 1`] = `
<div>
<div
class="ant-card ant-card-bordered"
>
<div
class="ant-card-head"
style="color: red;"
>
<div
class="ant-card-head-wrapper"
>
<div
class="ant-card-head-title"
>
Card title
</div>
</div>
</div>
<div
class="ant-card-body"
>
<p>
Card content
</p>
</div>
</div>
</div>
`;
18 changes: 18 additions & 0 deletions components/card/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,22 @@ describe('Card', () => {

expect(container.firstChild).toMatchSnapshot();
});

it('should support custom className', () => {
const { container } = render(
<Card title="Card title" classNames={{ header: 'custom-head' }}>
<p>Card content</p>
</Card>,
);
expect(container).toMatchSnapshot();
});

it('should support custom styles', () => {
const { container } = render(
<Card title="Card title" styles={{ header: { color: 'red' } }}>
<p>Card content</p>
</Card>,
);
expect(container).toMatchSnapshot();
});
});
15 changes: 13 additions & 2 deletions components/card/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,10 @@ Common props ref:[Common props](/docs/react/common-props)
| --- | --- | --- | --- | --- |
| actions | The action list, shows at the bottom of the Card | Array&lt;ReactNode> | - | |
| activeTabKey | Current TabPane's key | string | - | |
| bodyStyle | Inline style to apply to the card content | CSSProperties | - | |
| bordered | Toggles rendering of the border around the card | boolean | true | |
| cover | Card cover | ReactNode | - | |
| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set | string | - | |
| extra | Content to render in the top-right corner of the card | ReactNode | - | |
| headStyle | Inline style to apply to the card head | CSSProperties | - | |
| hoverable | Lift up when hovering card | boolean | false | |
| loading | Shows a loading indicator while the contents of the card are being fetched | boolean | false | |
| size | Size of card | `default` \| `small` | `default` | |
Expand All @@ -55,6 +53,8 @@ Common props ref:[Common props](/docs/react/common-props)
| tabProps | [Tabs](/components/tabs/#tabs) | - | - | |
| title | Card title | ReactNode | - | |
| type | Card style type, can be set to `inner` or not set | string | - | |
| classNames | Config Card build-in module's className | Record<SemanticDOM, string> | - | 5.13.0 |
li-jia-nan marked this conversation as resolved.
Show resolved Hide resolved
| styles | Config Card build-in module's style | Record<SemanticDOM, string> | - | 5.13.0 |
| onTabChange | Callback when tab is switched | (key) => void | - | |

### Card.Grid
Expand All @@ -75,6 +75,17 @@ Common props ref:[Common props](/docs/react/common-props)
| style | The style object of container | CSSProperties | - | |
| title | Title content | ReactNode | - | |

### `styles` 和 `classNames` attribute

| 名称 | 说明 | 版本 |
| ------- | --------------------- | ------ |
| header | set `header` of card | 5.13.0 |
| body | set `body` of card | 5.13.0 |
| extra | set `extra` of card | 5.13.0 |
| title | set `title` of card | 5.13.0 |
| actions | set `actions` of card | 5.13.0 |
| cover | set `cover` of card | 5.13.0 |

## Design Token

<ComponentTokenTable component="Card"></ComponentTokenTable>
15 changes: 13 additions & 2 deletions components/card/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,10 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-8zR6rrupgAAA
| --- | --- | --- | --- | --- |
| actions | 卡片操作组,位置在卡片底部 | Array&lt;ReactNode> | - | |
| activeTabKey | 当前激活页签的 key | string | - | |
| bodyStyle | 内容区域自定义样式 | CSSProperties | - | |
| bordered | 是否有边框 | boolean | true | |
| cover | 卡片封面 | ReactNode | - | |
| defaultActiveTabKey | 初始化选中页签的 key,如果没有设置 activeTabKey | string | `第一个页签` | |
| extra | 卡片右上角的操作区域 | ReactNode | - | |
| headStyle | 自定义标题区域样式 | CSSProperties | - | |
| hoverable | 鼠标移过时可浮起 | boolean | false | |
| loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false | |
| size | card 的尺寸 | `default` \| `small` | `default` | |
Expand All @@ -56,6 +54,8 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-8zR6rrupgAAA
| tabProps | [Tabs](/components/tabs-cn#tabs) | - | - | |
| title | 卡片标题 | ReactNode | - | |
| type | 卡片类型,可设置为 `inner` 或 不设置 | string | - | |
| classNames | 配置卡片内置模块的 className | Record<SemanticDOM, string> | - | 5.13.0 |
| styles | 配置卡片内置模块的 style | Record<SemanticDOM, string> | - | 5.13.0 |
| onTabChange | 页签切换的回调 | (key) => void | - | |

### Card.Grid
Expand All @@ -76,6 +76,17 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*a-8zR6rrupgAAA
| style | 定义容器类名的样式 | CSSProperties | - | |
| title | 标题内容 | ReactNode | - | |

### `styles` 和 `classNames` 属性

| 名称 | 说明 | 版本 |
| ------- | ------------------------ | ------ |
| header | 设置卡片头部区域 | 5.13.0 |
| body | 设置卡片内容区域 | 5.13.0 |
| extra | 设置卡片右上角的操作区域 | 5.13.0 |
| title | 设置卡片标题 | 5.13.0 |
| actions | 设置卡片底部操作组 | 5.13.0 |
| cover | 设置标题封面 | 5.13.0 |

## 主题变量(Design Token)

<ComponentTokenTable component="Card"></ComponentTokenTable>
14 changes: 12 additions & 2 deletions components/config-provider/__tests__/style.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1002,15 +1002,25 @@ describe('ConfigProvider support style and className props', () => {
);
});

it('Should Card className & style works', () => {
it('Should Card className & style & classNames & styles works', () => {
const { container } = render(
<ConfigProvider card={{ className: 'cp-card', style: { backgroundColor: 'blue' } }}>
<ConfigProvider
card={{
className: 'cp-card',
style: { backgroundColor: 'blue' },
classNames: { body: 'custom-body' },
styles: { body: { color: 'red' } },
}}
>
<Card>test</Card>
</ConfigProvider>,
);
const element = container.querySelector<HTMLDivElement>('.ant-card');
expect(element).toHaveClass('cp-card');
expect(element).toHaveStyle({ backgroundColor: 'blue' });
const head = container.querySelector<HTMLDivElement>('.ant-card-body');
expect(head).toHaveClass('custom-body');
expect(head).toHaveStyle({ color: 'red' });
});

it('Should Tabs className & style works', () => {
Expand Down
8 changes: 7 additions & 1 deletion components/config-provider/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { Locale } from '../locale';
import type { ModalProps } from '../modal';
import type { SpaceProps } from '../space';
import type { TabsProps } from '../tabs';
import type { CardProps } from '../card';
import type { AliasToken, MappingAlgorithm, OverrideToken } from '../theme/interface';
import type { RenderEmptyHandler } from './defaultRenderEmpty';
import type { SizeType } from './SizeContext';
Expand Down Expand Up @@ -81,6 +82,11 @@ export interface ButtonConfig extends ComponentStyleConfig {
styles?: ButtonProps['styles'];
}

export interface CardConfig extends ComponentStyleConfig {
classNames?: CardProps['classNames'];
styles: CardProps['styles'];
}

export type DrawerConfig = ComponentStyleConfig &
Pick<DrawerProps, 'classNames' | 'styles' | 'closeIcon'>;

Expand Down Expand Up @@ -172,7 +178,7 @@ export interface ConfigConsumerProps {
message?: ComponentStyleConfig;
tag?: ComponentStyleConfig;
table?: ComponentStyleConfig;
card?: ComponentStyleConfig;
card?: CardConfig;
tabs?: ComponentStyleConfig & Pick<TabsProps, 'indicator' | 'indicatorSize'>;
timeline?: ComponentStyleConfig;
timePicker?: ComponentStyleConfig;
Expand Down
2 changes: 1 addition & 1 deletion components/config-provider/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const {
| badge | Set Badge common props | { className?: string, style?: React.CSSProperties, classNames?: { count?: string, indicator?: string }, styles?: { count?: React.CSSProperties, indicator?: React.CSSProperties } } | - | 5.7.0 |
| breadcrumb | Set Breadcrumb common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| button | Set Button common props | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties } } | - | 5.6.0 |
| card | Set Card common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| card | Set Card common props | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card#api), styles?: [CardProps\["styles"\]](/components/card#api) } | - | 5.7.0, `classNames` and `styles`: 5.13.0 |
| calendar | Set Calendar common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| carousel | Set Carousel common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| cascader | Set Cascader common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
Expand Down