diff --git a/components/_util/hooks/useRef.ts b/components/_util/hooks/useRef.ts deleted file mode 100644 index 86f6512ecd..0000000000 --- a/components/_util/hooks/useRef.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Ref } from 'vue'; -import { onBeforeUpdate, ref } from 'vue'; - -export type UseRef = [(el: any, key: string | number) => void, Ref]; -export type Refs = Record; -export const useRef = (): UseRef => { - const refs = ref({}); - const setRef = (el: any, key: string | number) => { - refs.value[key] = el; - }; - onBeforeUpdate(() => { - refs.value = {}; - }); - return [setRef, refs]; -}; - -export default useRef; diff --git a/components/_util/hooks/useRefs.ts b/components/_util/hooks/useRefs.ts new file mode 100644 index 0000000000..965805e203 --- /dev/null +++ b/components/_util/hooks/useRefs.ts @@ -0,0 +1,20 @@ +import type { Ref, ComponentPublicInstance } from 'vue'; +import { onBeforeUpdate, ref } from 'vue'; +import type { Key } from '../type'; + +type RefType = HTMLElement | ComponentPublicInstance; +export type RefsValue = Map; +type UseRef = [(key: Key) => (el: RefType) => void, Ref]; +const useRefs = (): UseRef => { + const refs = ref(new Map()); + + const setRef = (key: Key) => (el: RefType) => { + refs.value.set(key, el); + }; + onBeforeUpdate(() => { + refs.value = new Map(); + }); + return [setRef, refs]; +}; + +export default useRefs; diff --git a/components/_util/hooks/useState.ts b/components/_util/hooks/useState.ts index a74476a9e2..5276ab44a2 100644 --- a/components/_util/hooks/useState.ts +++ b/components/_util/hooks/useState.ts @@ -2,7 +2,7 @@ import type { Ref } from 'vue'; import { ref } from 'vue'; export default function useState>( - defaultStateValue: T | (() => T), + defaultStateValue?: T | (() => T), ): [R, (val: T) => void] { const initValue: T = typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue; diff --git a/components/card/Card.tsx b/components/card/Card.tsx index 88d1d51593..e968d7999f 100644 --- a/components/card/Card.tsx +++ b/components/card/Card.tsx @@ -1,68 +1,62 @@ import type { VNodeTypes, PropType, VNode, ExtractPropTypes } from 'vue'; -import { inject, isVNode, defineComponent } from 'vue'; -import { tuple } from '../_util/type'; +import { isVNode, defineComponent, renderSlot } from 'vue'; import Tabs from '../tabs'; import Row from '../row'; import Col from '../col'; import PropTypes from '../_util/vue-types'; -import { getComponent, getSlot, isEmptyElement } from '../_util/props-util'; +import { flattenChildren, isEmptyElement } from '../_util/props-util'; import BaseMixin from '../_util/BaseMixin'; -import { defaultConfigProvider } from '../config-provider'; +import type { SizeType } from '../config-provider'; import isPlainObject from 'lodash-es/isPlainObject'; - +import useConfigInject from '../_util/hooks/useConfigInject'; +import devWarning from '../vc-util/devWarning'; export interface CardTabListType { key: string; - tab: VNodeTypes; + tab: any; + /** @deprecated Please use `customTab` instead. */ slots?: { tab: string }; disabled?: boolean; } export type CardType = 'inner'; +export type CardSize = 'default' | 'small'; const { TabPane } = Tabs; -const cardProps = { +const cardProps = () => ({ prefixCls: PropTypes.string, - title: PropTypes.VNodeChild, - extra: PropTypes.VNodeChild, + title: PropTypes.any, + extra: PropTypes.any, bordered: PropTypes.looseBool.def(true), bodyStyle: PropTypes.style, headStyle: PropTypes.style, loading: PropTypes.looseBool.def(false), hoverable: PropTypes.looseBool.def(false), - type: PropTypes.string, - size: PropTypes.oneOf(tuple('default', 'small')), - actions: PropTypes.VNodeChild, + type: { type: String as PropType }, + size: { type: String as PropType }, + actions: PropTypes.any, tabList: { type: Array as PropType, }, - tabBarExtraContent: PropTypes.VNodeChild, + tabBarExtraContent: PropTypes.any, activeTabKey: PropTypes.string, defaultActiveTabKey: PropTypes.string, - cover: PropTypes.VNodeChild, + cover: PropTypes.any, onTabChange: { type: Function as PropType<(key: string) => void>, }, -}; +}); -export type CardProps = Partial>; +export type CardProps = Partial>>; const Card = defineComponent({ name: 'ACard', mixins: [BaseMixin], - props: cardProps, - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - data() { - return { - widerPadding: false, - }; - }, - methods: { - getAction(actions: VNodeTypes[]) { + props: cardProps(), + slots: ['title', 'extra', 'tabBarExtraContent', 'actions', 'cover', 'customTab'], + setup(props, { slots }) { + const { prefixCls, direction, size } = useConfigInject('card', props); + const getAction = (actions: VNodeTypes[]) => { const actionList = actions.map((action, index) => (isVNode(action) && !isEmptyElement(action)) || !isVNode(action) ? (
  • @@ -71,11 +65,11 @@ const Card = defineComponent({ ) : null, ); return actionList; - }, - triggerTabChange(key: string) { - this.$emit('tabChange', key); - }, - isContainGrid(obj: VNode[] = []) { + }; + const triggerTabChange = (key: string) => { + props.onTabChange?.(key); + }; + const isContainGrid = (obj: VNode[] = []) => { let containGrid: boolean; obj.forEach(element => { if (element && isPlainObject(element.type) && (element.type as any).__ANT_CARD_GRID) { @@ -83,145 +77,129 @@ const Card = defineComponent({ } }); return containGrid; - }, - }, - render() { - const { - prefixCls: customizePrefixCls, - headStyle = {}, - bodyStyle = {}, - loading, - bordered = true, - size = 'default', - type, - tabList, - hoverable, - activeTabKey, - defaultActiveTabKey, - } = this.$props; - const { $slots } = this; - const children = getSlot(this); - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('card', customizePrefixCls); - - const tabBarExtraContent = getComponent(this, 'tabBarExtraContent'); - const classString = { - [`${prefixCls}`]: true, - [`${prefixCls}-loading`]: loading, - [`${prefixCls}-bordered`]: bordered, - [`${prefixCls}-hoverable`]: !!hoverable, - [`${prefixCls}-contain-grid`]: this.isContainGrid(children), - [`${prefixCls}-contain-tabs`]: tabList && tabList.length, - [`${prefixCls}-${size}`]: size !== 'default', - [`${prefixCls}-type-${type}`]: !!type, }; - const loadingBlockStyle = - bodyStyle.padding === 0 || bodyStyle.padding === '0px' ? { padding: 24 } : undefined; + return () => { + const { + headStyle = {}, + bodyStyle = {}, + loading, + bordered = true, + type, + tabList, + hoverable, + activeTabKey, + defaultActiveTabKey, + tabBarExtraContent = slots.tabBarExtraContent?.(), + title = slots.title?.(), + extra = slots.extra?.(), + actions = slots.actions?.(), + cover = slots.cover?.(), + } = props; + const children = flattenChildren(slots.default?.()); + const pre = prefixCls.value; + const classString = { + [`${pre}`]: true, + [`${pre}-loading`]: loading, + [`${pre}-bordered`]: bordered, + [`${pre}-hoverable`]: !!hoverable, + [`${pre}-contain-grid`]: isContainGrid(children), + [`${pre}-contain-tabs`]: tabList && tabList.length, + [`${pre}-${size.value}`]: size.value, + [`${pre}-type-${type}`]: !!type, + [`${pre}-rtl`]: direction.value === 'rtl', + }; - const loadingBlock = ( -
    - - -
    - - - - -
    - - -
    - - - - -
    - - -
    - - - - -
    - - -
    - - - - -
    - - -
    - - -
    - - -
    - ); + const loadingBlockStyle = + bodyStyle.padding === 0 || bodyStyle.padding === '0px' ? { padding: '24px' } : undefined; - const hasActiveTabKey = activeTabKey !== undefined; - const tabsProps = { - size: 'large', - [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey - ? activeTabKey - : defaultActiveTabKey, - tabBarExtraContent, - onChange: this.triggerTabChange, - class: `${prefixCls}-head-tabs`, - }; + const block =
    ; + const loadingBlock = ( +
    + + {block} + + + {block} + {block} + + + {block} + {block} + + + {block} + {block} + + + {block} + {block} + {block} + +
    + ); - let head; - const tabs = - tabList && tabList.length ? ( - - {tabList.map(item => { - const { tab: temp, slots } = item as CardTabListType; - const name = slots?.tab; - const tab = temp !== undefined ? temp : $slots[name] ? $slots[name](item) : null; - return ; - })} - - ) : null; - const titleDom = getComponent(this, 'title'); - const extraDom = getComponent(this, 'extra'); - if (titleDom || extraDom || tabs) { - head = ( -
    -
    - {titleDom &&
    {titleDom}
    } - {extraDom &&
    {extraDom}
    } + const hasActiveTabKey = activeTabKey !== undefined; + const tabsProps = { + size: 'large' as SizeType, + [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey + ? activeTabKey + : defaultActiveTabKey, + onChange: triggerTabChange, + class: `${pre}-head-tabs`, + }; + + let head; + const tabs = + tabList && tabList.length ? ( + tabBarExtraContent : null }} + > + {tabList.map(item => { + const { tab: temp, slots: itemSlots } = item as CardTabListType; + const name = itemSlots?.tab; + devWarning( + !itemSlots, + 'Card', + `tabList slots is deprecated, Please use \`customTab\` instead.`, + ); + let tab = temp !== undefined ? temp : slots[name] ? slots[name](item) : null; + tab = renderSlot(slots, 'customTab', item as any, () => [tab]); + return ; + })} + + ) : null; + if (title || extra || tabs) { + head = ( +
    +
    + {title &&
    {title}
    } + {extra &&
    {extra}
    } +
    + {tabs}
    - {tabs} + ); + } + + const coverDom = cover ?
    {cover}
    : null; + const body = ( +
    + {loading ? loadingBlock : children}
    ); - } + const actionDom = + actions && actions.length ?
      {getAction(actions)}
    : null; - const cover = getComponent(this, 'cover'); - const coverDom = cover ?
    {cover}
    : null; - const body = ( -
    - {loading ? loadingBlock : children} -
    - ); - const actions = getComponent(this, 'actions'); - const actionDom = - actions && actions.length ? ( -
      {this.getAction(actions)}
    - ) : null; - - return ( -
    - {head} - {coverDom} - {children ? body : null} - {actionDom} -
    - ); + return ( +
    + {head} + {coverDom} + {children && children.length ? body : null} + {actionDom} +
    + ); + }; }, }); diff --git a/components/card/Grid.tsx b/components/card/Grid.tsx index c5692703ad..ba4156b9d3 100644 --- a/components/card/Grid.tsx +++ b/components/card/Grid.tsx @@ -1,30 +1,23 @@ -import { defineComponent, inject } from 'vue'; -import PropTypes from '../_util/vue-types'; -import { defaultConfigProvider } from '../config-provider'; -import { getSlot } from '../_util/props-util'; +import { defineComponent, computed } from 'vue'; +import useConfigInject from '../_util/hooks/useConfigInject'; export default defineComponent({ name: 'ACardGrid', __ANT_CARD_GRID: true, props: { - prefixCls: PropTypes.string, - hoverable: PropTypes.looseBool, + prefixCls: String, + hoverable: { type: Boolean, default: true }, }, - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), + setup(props, { slots }) { + const { prefixCls } = useConfigInject('card', props); + const classNames = computed(() => { + return { + [`${prefixCls.value}-grid`]: true, + [`${prefixCls.value}-grid-hoverable`]: props.hoverable, + }; + }); + return () => { + return
    {slots.default?.()}
    ; }; }, - render() { - const { prefixCls: customizePrefixCls, hoverable = true } = this.$props; - - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('card', customizePrefixCls); - - const classString = { - [`${prefixCls}-grid`]: true, - [`${prefixCls}-grid-hoverable`]: hoverable, - }; - return
    {getSlot(this)}
    ; - }, }); diff --git a/components/card/Meta.tsx b/components/card/Meta.tsx index f72238e1a7..af2f13d269 100644 --- a/components/card/Meta.tsx +++ b/components/card/Meta.tsx @@ -1,52 +1,47 @@ -import { defineComponent, inject } from 'vue'; +import { defineComponent } from 'vue'; import PropTypes from '../_util/vue-types'; -import { getComponent } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; +import { getPropsSlot } from '../_util/props-util'; +import useConfigInject from '../_util/hooks/useConfigInject'; export default defineComponent({ name: 'ACardMeta', props: { prefixCls: PropTypes.string, - title: PropTypes.VNodeChild, - description: PropTypes.VNodeChild, - avatar: PropTypes.VNodeChild, + title: PropTypes.any, + description: PropTypes.any, + avatar: PropTypes.any, }, - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - render() { - const { prefixCls: customizePrefixCls } = this.$props; - - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('card', customizePrefixCls); - - const classString = { - [`${prefixCls}-meta`]: true, - }; + slots: ['title', 'description', 'avatar'], + setup(props, { slots }) { + const { prefixCls } = useConfigInject('card', props); + return () => { + const classString = { + [`${prefixCls.value}-meta`]: true, + }; + const avatar = getPropsSlot(slots, props, 'avatar'); + const title = getPropsSlot(slots, props, 'title'); + const description = getPropsSlot(slots, props, 'description'); - const avatar = getComponent(this, 'avatar'); - const title = getComponent(this, 'title'); - const description = getComponent(this, 'description'); - - const avatarDom = avatar ?
    {avatar}
    : null; - const titleDom = title ?
    {title}
    : null; - const descriptionDom = description ? ( -
    {description}
    - ) : null; - const MetaDetail = - titleDom || descriptionDom ? ( -
    - {titleDom} - {descriptionDom} -
    + const avatarDom = avatar ? ( +
    {avatar}
    + ) : null; + const titleDom = title ?
    {title}
    : null; + const descriptionDom = description ? ( +
    {description}
    ) : null; - return ( -
    - {avatarDom} - {MetaDetail} -
    - ); + const MetaDetail = + titleDom || descriptionDom ? ( +
    + {titleDom} + {descriptionDom} +
    + ) : null; + return ( +
    + {avatarDom} + {MetaDetail} +
    + ); + }; }, }); diff --git a/components/card/__tests__/__snapshots__/demo.test.js.snap b/components/card/__tests__/__snapshots__/demo.test.js.snap index 4e2733b3ad..4cc8d52c06 100644 --- a/components/card/__tests__/__snapshots__/demo.test.js.snap +++ b/components/card/__tests__/__snapshots__/demo.test.js.snap @@ -294,35 +294,40 @@ exports[`renders ./components/card/demo/tabs.vue correctly 1`] = `
    Card title
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    +
    +
    + +
    +
    +
    + + +
    +
    + +
    + +
    -
    -
    -
    -
    -
    -
    -
    - +
    +
    +
    + +
    + +
    -
    @@ -335,42 +340,47 @@ exports[`renders ./components/card/demo/tabs.vue correctly 1`] = `
    -
    -
    - -
    -
    -
    -
    -
    - - - -
    -
    -
    +
    +
    + +
    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    -
    -
    -
    -
    diff --git a/components/card/__tests__/__snapshots__/index.test.js.snap b/components/card/__tests__/__snapshots__/index.test.js.snap index bf48381bf5..5c0c2a3007 100644 --- a/components/card/__tests__/__snapshots__/index.test.js.snap +++ b/components/card/__tests__/__snapshots__/index.test.js.snap @@ -5,7 +5,7 @@ exports[`Card should still have padding when card which set padding to 0 is load
    -
    +
    diff --git a/components/card/demo/tabs.vue b/components/card/demo/tabs.vue index 890fd69d2c..5be91cd8bb 100644 --- a/components/card/demo/tabs.vue +++ b/components/card/demo/tabs.vue @@ -24,8 +24,8 @@ More content can be hosted :active-tab-key="key" @tabChange="key => onTabChange(key, 'key')" > -