diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index 2d46d036f..a6528d23f 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -15,6 +15,7 @@ export default ( direction: ComputedRef; size: ComputedRef; getTargetContainer: ComputedRef<() => HTMLElement>; + space: ComputedRef<{ size: SizeType | number }>; } => { const configProvider = inject>( 'configProvider', @@ -22,7 +23,8 @@ export default ( ); const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); const direction = computed(() => configProvider.direction); + const space = computed(() => configProvider.space); const size = computed(() => props.size || configProvider.componentSize); const getTargetContainer = computed(() => props.getTargetContainer); - return { configProvider, prefixCls, direction, size, getTargetContainer }; + return { configProvider, prefixCls, direction, size, getTargetContainer, space }; }; diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 232012bb5..b745b300a 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -95,7 +95,7 @@ export const configProviderProps = { type: String as PropType<'ltr' | 'rtl'>, }, space: { - type: [String, Number] as PropType, + type: Object as PropType<{ size: SizeType | number }>, }, virtual: PropTypes.looseBool, dropdownMatchSelectWidth: PropTypes.looseBool, diff --git a/components/space/index.tsx b/components/space/index.tsx index b7c73bb1e..46d5e25ce 100644 --- a/components/space/index.tsx +++ b/components/space/index.tsx @@ -1,74 +1,131 @@ -import { inject, defineComponent, PropType } from 'vue'; +import { + defineComponent, + PropType, + ExtractPropTypes, + computed, + ref, + watch, + CSSProperties, +} from 'vue'; import PropTypes from '../_util/vue-types'; import { filterEmpty } from '../_util/props-util'; -import { defaultConfigProvider, SizeType } from '../config-provider'; +import { SizeType } from '../config-provider'; import { tuple, withInstall } from '../_util/type'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import useFlexGapSupport from '../_util/hooks/useFlexGapSupport'; +import classNames from '../_util/classNames'; +export type SpaceSize = SizeType | number; const spaceSize = { small: 8, middle: 16, large: 24, }; +const spaceProps = { + prefixCls: PropTypes.string, + size: { + type: [String, Number, Array] as PropType, + }, + direction: PropTypes.oneOf(tuple('horizontal', 'vertical')).def('horizontal'), + align: PropTypes.oneOf(tuple('start', 'end', 'center', 'baseline')), + wrap: PropTypes.looseBool, +}; + +export type SpaceProps = Partial>; + +function getNumberSize(size: SpaceSize) { + return typeof size === 'string' ? spaceSize[size] : size || 0; +} const Space = defineComponent({ name: 'ASpace', - props: { - prefixCls: PropTypes.string, - size: { - type: [String, Number] as PropType, - }, - direction: PropTypes.oneOf(tuple('horizontal', 'vertical')), - align: PropTypes.oneOf(tuple('start', 'end', 'center', 'baseline')), - }, + props: spaceProps, + slots: ['split'], setup(props, { slots }) { - const configProvider = inject('configProvider', defaultConfigProvider); + const { prefixCls, space, direction: directionConfig } = useConfigInject('space', props); + const supportFlexGap = useFlexGapSupport(); + const size = computed(() => props.size || space.value?.size || 'small'); + const horizontalSize = ref(); + const verticalSize = ref(); + watch( + size, + () => { + [horizontalSize.value, verticalSize.value] = ((Array.isArray(size.value) + ? size.value + : [size.value, size.value]) as [SpaceSize, SpaceSize]).map(item => getNumberSize(item)); + }, + { immediate: true }, + ); + const mergedAlign = computed(() => + props.align === undefined && props.direction === 'horizontal' ? 'center' : props.align, + ); + const cn = computed(() => { + return classNames(prefixCls.value, `${prefixCls.value}-${props.direction}`, { + [`${prefixCls.value}-rtl`]: directionConfig.value === 'rtl', + [`${prefixCls.value}-align-${mergedAlign.value}`]: mergedAlign.value, + }); + }); + + const marginDirection = computed(() => + directionConfig.value === 'rtl' ? 'marginLeft' : 'marginRight', + ); + const style = computed(() => { + const gapStyle: CSSProperties = {}; + if (supportFlexGap) { + gapStyle.columnGap = `${horizontalSize.value}px`; + gapStyle.rowGap = `${verticalSize.value}px`; + } + return { + ...gapStyle, + ...(props.wrap && { flexWrap: 'wrap', marginBottom: `${-verticalSize.value}px` }), + } as CSSProperties; + }); return () => { - const { - align, - size = 'small', - direction = 'horizontal', - prefixCls: customizePrefixCls, - } = props; + const { wrap, direction = 'horizontal' } = props; - const { getPrefixCls } = configProvider; - const prefixCls = getPrefixCls('space', customizePrefixCls); const items = filterEmpty(slots.default?.()); const len = items.length; if (len === 0) { return null; } - - const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; - - const someSpaceClass = { - [prefixCls]: true, - [`${prefixCls}-${direction}`]: true, - [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, - }; - - const itemClassName = `${prefixCls}-item`; - const marginDirection = 'marginRight'; // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; - + const split = slots.split?.(); + const itemClassName = `${prefixCls.value}-item`; + const horizontalSizeVal = horizontalSize.value; + const latestIndex = len - 1; return ( -
- {items.map((child, i) => ( -
+ {items.map((child, index) => { + let itemStyle: CSSProperties = {}; + if (!supportFlexGap) { + if (direction === 'vertical') { + if (index < latestIndex) { + itemStyle = { marginBottom: `${horizontalSizeVal / (split ? 2 : 1)}px` }; + } + } else { + itemStyle = { + ...(index < latestIndex && { + [marginDirection.value]: `${horizontalSizeVal / (split ? 2 : 1)}px`, + }), + ...(wrap && { paddingBottom: `${verticalSize.value}px` }), + }; } - > - {child} -
- ))} + } + + return ( + <> +
+ {child} +
+ {index < latestIndex && split && ( + + {split} + + )} + + ); + })}
); }; diff --git a/components/space/style/index.less b/components/space/style/index.less index 953c75d0b..c73c66984 100644 --- a/components/space/style/index.less +++ b/components/space/style/index.less @@ -2,6 +2,7 @@ @import '../../style/mixins/index'; @space-prefix-cls: ~'@{ant-prefix}-space'; +@space-item-prefix-cls: ~'@{ant-prefix}-space-item'; .@{space-prefix-cls} { display: inline-flex; @@ -25,4 +26,10 @@ } } -// @import './rtl'; +.@{space-item-prefix-cls} { + &:empty { + display: none; + } +} + +@import './rtl'; diff --git a/components/space/style/rtl.less b/components/space/style/rtl.less new file mode 100644 index 000000000..75aa411b7 --- /dev/null +++ b/components/space/style/rtl.less @@ -0,0 +1,10 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; + +@space-prefix-cls: ~'@{ant-prefix}-space'; + +.@{space-prefix-cls} { + &-rtl { + direction: rtl; + } +}