diff --git a/components/space/Item.tsx b/components/space/Item.tsx index cf32a9e639dc..cec88066062f 100644 --- a/components/space/Item.tsx +++ b/components/space/Item.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; + import { SpaceContext } from './context'; +import type { SpaceContextType } from './context'; export interface ItemProps { className: string; @@ -23,7 +25,7 @@ const Item: React.FC = ({ style: customStyle, }) => { const { horizontalSize, verticalSize, latestIndex, supportFlexGap } = - React.useContext(SpaceContext); + React.useContext(SpaceContext); let style: React.CSSProperties = {}; @@ -34,6 +36,7 @@ const Item: React.FC = ({ } } else { style = { + // Compatible IE, cannot use `marginInlineEnd` ...(index < latestIndex && { [marginDirection]: horizontalSize / (split ? 2 : 1) }), ...(wrap && { paddingBottom: verticalSize }), }; diff --git a/components/space/__tests__/gap.test.tsx b/components/space/__tests__/gap.test.tsx index 34a8fd956289..2fc7e0063e33 100644 --- a/components/space/__tests__/gap.test.tsx +++ b/components/space/__tests__/gap.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import Space from '..'; import { render } from '../../../tests/utils'; @@ -16,15 +17,31 @@ describe('flex gap', () => { , ); - expect( - container.querySelector('div.ant-space')?.style[ - 'column-gap' as keyof CSSStyleDeclaration - ], - ).toBe('8px'); - expect( - container.querySelector('div.ant-space')?.style[ - 'row-gap' as keyof CSSStyleDeclaration - ], - ).toBe('8px'); + expect(container.querySelector('div.ant-space')).toHaveClass( + 'ant-space-gap-row-small', + ); + expect(container.querySelector('div.ant-space')).toHaveClass( + 'ant-space-gap-col-small', + ); + }); + + it('should size work', () => { + const { container } = render( + + test + , + ); + const element = container.querySelector('div.ant-space'); + expect(element).toHaveStyle({ rowGap: '10px', columnGap: '10px' }); + }); + + it('should NaN work', () => { + expect(() => { + render( + + test + , + ); + }).not.toThrow(); }); }); diff --git a/components/space/__tests__/index.test.tsx b/components/space/__tests__/index.test.tsx index de758e70ed71..74b54836cc31 100644 --- a/components/space/__tests__/index.test.tsx +++ b/components/space/__tests__/index.test.tsx @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import React, { useState } from 'react'; + import Space from '..'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; @@ -66,25 +67,9 @@ describe('Space', () => { , ); - expect(container.querySelector('div.ant-space-item')?.style.marginRight).toBe( - '10px', - ); - expect( - container.querySelectorAll('div.ant-space-item')[1]?.style.marginRight, - ).toBe(''); - }); - - it('should render width size 0', () => { - const { container } = render( - - 1 - 2 - , - ); - - expect(container.querySelector('div.ant-space-item')?.style.marginRight).toBe( - '0px', - ); + const items = container.querySelectorAll('div.ant-space-item'); + expect(items[0]?.style.marginRight).toBe('10px'); + expect(items[1]?.style.marginRight).toBe(''); }); it('should render vertical space width customize size', () => { @@ -95,12 +80,9 @@ describe('Space', () => { , ); - expect(container.querySelector('div.ant-space-item')?.style.marginBottom).toBe( - '10px', - ); - expect( - container.querySelectorAll('div.ant-space-item')[1]?.style.marginBottom, - ).toBe(''); + const items = container.querySelectorAll('div.ant-space-item'); + expect(items[0]?.style.marginBottom).toBe('10px'); + expect(items[1]?.style.marginBottom).toBe(''); }); it('should render correct with children', () => { @@ -199,16 +181,15 @@ describe('Space', () => { }); it('should render the hidden empty item wrapper', () => { - const Null = () => null; + const Null: React.FC = () => null; const { container } = render( , ); - const item = container.querySelector('div.ant-space-item') as HTMLElement; - - expect(item).toBeEmptyDOMElement(); - expect(getComputedStyle(item).display).toBe('none'); + const element = container.querySelector('div.ant-space-item')!; + expect(element).toBeEmptyDOMElement(); + expect(getComputedStyle(element).display).toBe('none'); }); it('should ref work', () => { @@ -231,25 +212,18 @@ describe('Space', () => { , ); - expect(container.querySelector('.ant-space-item.test-classNames')).toBeTruthy(); + expect(container.querySelector('.ant-space-item.test-classNames')).toBeTruthy(); }); it('should styles work', () => { const { container } = render( - + Text1 Text2 , ); - - expect(container.querySelector('.ant-space-item')?.getAttribute('style')).toEqual( - 'margin-right: 8px; color: red;', - ); + expect( + container.querySelector('.ant-space-item')?.getAttribute('style'), + ).toEqual('margin-right: 8px; color: red;'); }); }); diff --git a/components/space/context.ts b/components/space/context.ts index eba94e542a52..2ce119be6f8c 100644 --- a/components/space/context.ts +++ b/components/space/context.ts @@ -1,6 +1,13 @@ import React from 'react'; -export const SpaceContext = React.createContext({ +export interface SpaceContextType { + latestIndex: number; + horizontalSize: number; + verticalSize: number; + supportFlexGap: boolean; +} + +export const SpaceContext = React.createContext({ latestIndex: 0, horizontalSize: 0, verticalSize: 0, diff --git a/components/space/demo/base.tsx b/components/space/demo/base.tsx index 59b18a556f6b..fec82b5f4030 100644 --- a/components/space/demo/base.tsx +++ b/components/space/demo/base.tsx @@ -1,5 +1,5 @@ -import { UploadOutlined } from '@ant-design/icons'; import React from 'react'; +import { UploadOutlined } from '@ant-design/icons'; import { Button, Popconfirm, Space, Upload } from 'antd'; const App: React.FC = () => ( diff --git a/components/space/index.tsx b/components/space/index.tsx index 6bfe74fd15d6..17efa7c1d67b 100644 --- a/components/space/index.tsx +++ b/components/space/index.tsx @@ -1,16 +1,19 @@ 'use client'; +import * as React from 'react'; import classNames from 'classnames'; import toArray from 'rc-util/lib/Children/toArray'; -import * as React from 'react'; + import useFlexGapSupport from '../_util/hooks/useFlexGapSupport'; import { ConfigContext } from '../config-provider'; import type { SizeType } from '../config-provider/SizeContext'; +import { useToken } from '../theme/internal'; import Compact from './Compact'; -import Item from './Item'; - import { SpaceContextProvider } from './context'; +import type { SpaceContextType } from './context'; +import Item from './Item'; import useStyle from './style'; +import { getRealSize, isPresetSize } from './utils'; export { SpaceContext } from './context'; @@ -31,16 +34,6 @@ export interface SpaceProps extends React.HTMLAttributes { styles?: { item: React.CSSProperties }; } -const spaceSize = { - small: 8, - middle: 16, - large: 24, -}; - -function getNumberSize(size: SpaceSize) { - return typeof size === 'string' ? spaceSize[size] : size || 0; -} - const Space = React.forwardRef((props, ref) => { const { getPrefixCls, space, direction: directionConfig } = React.useContext(ConfigContext); @@ -60,23 +53,29 @@ const Space = React.forwardRef((props, ref) => { ...otherProps } = props; - const supportFlexGap = useFlexGapSupport(); + const [, token] = useToken(); - const [horizontalSize, verticalSize] = React.useMemo( - () => - ((Array.isArray(size) ? size : [size, size]) as [SpaceSize, SpaceSize]).map((item) => - getNumberSize(item), - ), - [size], - ); + const spaceSizeMap = { + small: token.paddingXS, + middle: token.padding, + large: token.paddingLG, + } as const; + + const [horizontalSize, verticalSize] = Array.isArray(size) ? size : ([size, size] as const); + + const realHorizontalSize = getRealSize(spaceSizeMap, horizontalSize); + + const realVerticalSize = getRealSize(spaceSizeMap, verticalSize); const childNodes = toArray(children, { keepEmpty: true }); + const supportFlexGap = useFlexGapSupport(); + const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; const prefixCls = getPrefixCls('space', customizePrefixCls); const [wrapSSR, hashId] = useStyle(prefixCls); - const cn = classNames( + const cls = classNames( prefixCls, space?.className, hashId, @@ -84,6 +83,8 @@ const Space = React.forwardRef((props, ref) => { { [`${prefixCls}-rtl`]: directionConfig === 'rtl', [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, + [`${prefixCls}-gap-row-${verticalSize}`]: supportFlexGap && isPresetSize(verticalSize), + [`${prefixCls}-gap-col-${horizontalSize}`]: supportFlexGap && isPresetSize(horizontalSize), }, className, rootClassName, @@ -98,7 +99,7 @@ const Space = React.forwardRef((props, ref) => { // Calculate latest one let latestIndex = 0; - const nodes = childNodes.map((child, i) => { + const nodes = childNodes.map((child, i) => { if (child !== null && child !== undefined) { latestIndex = i; } @@ -121,8 +122,13 @@ const Space = React.forwardRef((props, ref) => { ); }); - const spaceContext = React.useMemo( - () => ({ horizontalSize, verticalSize, latestIndex, supportFlexGap }), + const spaceContext = React.useMemo( + () => ({ + horizontalSize: realHorizontalSize, + verticalSize: realVerticalSize, + latestIndex, + supportFlexGap, + }), [horizontalSize, verticalSize, latestIndex, supportFlexGap], ); @@ -138,24 +144,20 @@ const Space = React.forwardRef((props, ref) => { // Patch for gap not support if (!supportFlexGap) { - gapStyle.marginBottom = -verticalSize; + gapStyle.marginBottom = -realVerticalSize; } } if (supportFlexGap) { - gapStyle.columnGap = horizontalSize; - gapStyle.rowGap = verticalSize; + gapStyle.columnGap = realHorizontalSize; + gapStyle.rowGap = realVerticalSize; } return wrapSSR(
{nodes} diff --git a/components/space/style/compact.tsx b/components/space/style/compact.tsx index d1027402dbd3..12590769cce5 100644 --- a/components/space/style/compact.tsx +++ b/components/space/style/compact.tsx @@ -1,4 +1,5 @@ import type { FullToken, GenerateStyle } from '../../theme/internal'; + /** Component only token. Which will handle additional calculation of alias token */ export interface ComponentToken { // Component token here diff --git a/components/space/style/index.tsx b/components/space/style/index.tsx index a83d8da9142c..c89ed08af4a0 100644 --- a/components/space/style/index.tsx +++ b/components/space/style/index.tsx @@ -1,5 +1,5 @@ import type { FullToken, GenerateStyle } from '../../theme/internal'; -import { genComponentStyleHook } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; import genSpaceCompactStyle from './compact'; /** Component only token. Which will handle additional calculation of alias token */ @@ -8,7 +8,9 @@ export interface ComponentToken { } interface SpaceToken extends FullToken<'Space'> { - // Custom token here + spaceGapSmallSize: number; + spaceGapMiddleSize: number; + spaceGapLargeSize: number; } const genSpaceStyle: GenerateStyle = (token) => { @@ -45,10 +47,47 @@ const genSpaceStyle: GenerateStyle = (token) => { }; }; +const genSpaceGapStyle: GenerateStyle = (token) => { + const { componentCls } = token; + return { + [componentCls]: { + '&-gap-row-small': { + rowGap: token.spaceGapSmallSize, + }, + '&-gap-row-middle': { + rowGap: token.spaceGapMiddleSize, + }, + '&-gap-row-large': { + rowGap: token.spaceGapLargeSize, + }, + '&-gap-col-small': { + columnGap: token.spaceGapSmallSize, + }, + '&-gap-col-middle': { + columnGap: token.spaceGapMiddleSize, + }, + '&-gap-col-large': { + columnGap: token.spaceGapLargeSize, + }, + }, + }; +}; + // ============================== Export ============================== export default genComponentStyleHook( 'Space', - (token) => [genSpaceStyle(token), genSpaceCompactStyle(token)], + (token) => { + const spaceToken = mergeToken(token, { + spaceGapSmallSize: token.paddingXS, + spaceGapMiddleSize: token.padding, + spaceGapLargeSize: token.paddingLG, + }); + return [ + genSpaceStyle(spaceToken), + genSpaceGapStyle(spaceToken), + genSpaceCompactStyle(spaceToken), + ]; + }, () => ({}), { // Space component don't apply extra font style diff --git a/components/space/utils.ts b/components/space/utils.ts new file mode 100644 index 000000000000..93f95cfcb913 --- /dev/null +++ b/components/space/utils.ts @@ -0,0 +1,27 @@ +import type { SpaceSize } from '.'; +import type { SizeType } from '../config-provider/SizeContext'; + +export function isPresetSize(size: SpaceSize): size is SizeType { + return ['small', 'middle', 'large'].includes(size as string); +} + +function isValidGapNumber(size: SpaceSize): size is number { + if (!size) { + // The case of size = 0 is deliberately excluded here, because the default value of the gap attribute in CSS is 0, so if the user passes 0 in, we can directly ignore it. + return false; + } + return typeof size === 'number' && !Number.isNaN(size); +} + +export const getRealSize = ( + sizeMap: Record, number>, + value: SpaceSize, +): number => { + if (isPresetSize(value)) { + return sizeMap[value!]; + } + if (isValidGapNumber(value)) { + return value; + } + return 0; +};