diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap index 8572ab796f..50f2581df9 100644 --- a/components/__tests__/__snapshots__/index.test.ts.snap +++ b/components/__tests__/__snapshots__/index.test.ts.snap @@ -27,6 +27,7 @@ exports[`antd exports modules correctly 1`] = ` "Drawer", "Dropdown", "Empty", + "Flex", "FloatButton", "Form", "Grid", diff --git a/components/config-provider/__tests__/style.test.tsx b/components/config-provider/__tests__/style.test.tsx index baad00b5d1..23f588b2c6 100644 --- a/components/config-provider/__tests__/style.test.tsx +++ b/components/config-provider/__tests__/style.test.tsx @@ -19,6 +19,7 @@ import Descriptions from '../../descriptions'; import Divider from '../../divider'; import Drawer from '../../drawer'; import Empty from '../../empty'; +import Flex from '../../flex'; import Form from '../../form'; import Image from '../../image'; import Input from '../../input'; @@ -1267,4 +1268,15 @@ describe('ConfigProvider support style and className props', () => { expect(container.querySelector('.ant-picker')).toHaveStyle('color: red; font-size: 16px;'); }); + + it('Should Flex className & style works', () => { + const { container } = render( + + test + , + ); + const element = container.querySelector('.ant-flex'); + expect(element).toHaveClass('cp-flex'); + expect(element).toHaveStyle({ backgroundColor: 'blue' }); + }); }); diff --git a/components/config-provider/context.ts b/components/config-provider/context.ts index 953d8ca2ea..d56003dfe8 100644 --- a/components/config-provider/context.ts +++ b/components/config-provider/context.ts @@ -6,6 +6,7 @@ import type { WarningContextProps } from '../_util/warning'; import type { ShowWaveEffect } from '../_util/wave/interface'; import type { BadgeProps } from '../badge'; import type { ButtonProps } from '../button'; +import type { FlexProps } from '../flex/interface'; import type { RequiredMark } from '../form/Form'; import type { InputProps } from '../input'; import type { Locale } from '../locale'; @@ -61,6 +62,10 @@ export interface ButtonConfig extends ComponentStyleConfig { styles?: ButtonProps['styles']; } +export interface FlexConfig extends ComponentStyleConfig { + vertical?: FlexProps['vertical']; +} + export type PopupOverflow = 'viewport' | 'scroll'; export interface WaveConfig { @@ -154,9 +159,8 @@ export interface ConfigConsumerProps { tree?: ComponentStyleConfig; colorPicker?: ComponentStyleConfig; datePicker?: ComponentStyleConfig; - + flex?: FlexConfig; wave?: WaveConfig; - warning?: WarningContextProps; } diff --git a/components/config-provider/index.en-US.md b/components/config-provider/index.en-US.md index aff467cf9e..f86820027b 100644 --- a/components/config-provider/index.en-US.md +++ b/components/config-provider/index.en-US.md @@ -122,6 +122,7 @@ const { | divider | Set Divider common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | drawer | Set Drawer common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | empty | Set Empty common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | +| flex | Set Flex common props | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 | | form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 | | image | Set Image common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | input | Set Input common props | { autoComplete?: string, className?: string, style?: React.CSSProperties } | - | 4.2.0 | diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 14d2195511..45c3f45821 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { createTheme } from '@ant-design/cssinjs'; import IconContext from '@ant-design/icons/lib/components/Context'; @@ -27,6 +29,7 @@ import type { ConfigConsumerProps, CSPConfig, DirectionType, + FlexConfig, PopupOverflow, Theme, ThemeConfig, @@ -194,7 +197,7 @@ export interface ConfigProviderProps { tree?: ComponentStyleConfig; colorPicker?: ComponentStyleConfig; datePicker?: ComponentStyleConfig; - + flex?: FlexConfig; /** * Wave is special component which only patch on the effect of component interaction. */ @@ -335,6 +338,7 @@ const ProviderChildren: React.FC = (props) => { tree, colorPicker, datePicker, + flex, wave, warning: warningConfig, } = props; @@ -425,6 +429,7 @@ const ProviderChildren: React.FC = (props) => { tree, colorPicker, datePicker, + flex, wave, warning: warningConfig, }; diff --git a/components/config-provider/index.zh-CN.md b/components/config-provider/index.zh-CN.md index 560be21251..7949c57954 100644 --- a/components/config-provider/index.zh-CN.md +++ b/components/config-provider/index.zh-CN.md @@ -124,6 +124,7 @@ const { | divider | 设置 Divider 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | drawer | 设置 Drawer 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | empty | 设置 Empty 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | +| flex | 设置 Flex 组件的通用属性 | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 | | form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 | | image | 设置 Image 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 | | input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties } | - | 5.7.0 | diff --git a/components/flex/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/flex/__tests__/__snapshots__/demo-extend.test.ts.snap new file mode 100644 index 0000000000..3b3773654d --- /dev/null +++ b/components/flex/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -0,0 +1,675 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/flex/demo/align.tsx extend context correctly 1`] = ` +
+

+ Select justify : +

+
+
+ + + + + + +
+
+

+ Select align : +

+
+
+ + + +
+
+
+ + + + +
+
+`; + +exports[`renders components/flex/demo/align.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/flex/demo/basic.tsx extend context correctly 1`] = ` +
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`renders components/flex/demo/basic.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/flex/demo/combination.tsx extend context correctly 1`] = ` +
+
+
+ avatar +
+

+ “antd is an enterprise-class UI design language and React UI library.” +

+ + + Get Start + + +
+
+
+
+`; + +exports[`renders components/flex/demo/combination.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/flex/demo/debug.tsx extend context correctly 1`] = ` +Array [ +
+
+
+
+
+
, +
+
+
+
+
+
, +] +`; + +exports[`renders components/flex/demo/debug.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/flex/demo/gap.tsx extend context correctly 1`] = ` +
+
+ + + + +
+
+ + + + +
+
+`; + +exports[`renders components/flex/demo/gap.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/flex/demo/wrap.tsx extend context correctly 1`] = ` +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+`; + +exports[`renders components/flex/demo/wrap.tsx extend context correctly 2`] = `[]`; diff --git a/components/flex/__tests__/__snapshots__/demo.test.ts.snap b/components/flex/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 0000000000..3bd7aec005 --- /dev/null +++ b/components/flex/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,663 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/flex/demo/align.tsx correctly 1`] = ` +
+

+ Select justify : +

+
+
+ + + + + + +
+
+

+ Select align : +

+
+
+ + + +
+
+
+ + + + +
+
+`; + +exports[`renders components/flex/demo/basic.tsx correctly 1`] = ` +
+
+ + +
+
+
+
+
+
+
+
+`; + +exports[`renders components/flex/demo/combination.tsx correctly 1`] = ` +
+
+
+ avatar +
+

+ “antd is an enterprise-class UI design language and React UI library.” +

+ + + Get Start + + +
+
+
+
+`; + +exports[`renders components/flex/demo/debug.tsx correctly 1`] = ` +Array [ +
+
+
+
+
+
, +
+
+
+
+
+
, +] +`; + +exports[`renders components/flex/demo/gap.tsx correctly 1`] = ` +
+
+ + + + +
+
+ + + + +
+
+`; + +exports[`renders components/flex/demo/wrap.tsx correctly 1`] = ` +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+`; diff --git a/components/flex/__tests__/__snapshots__/index.test.tsx.snap b/components/flex/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..6809df8adf --- /dev/null +++ b/components/flex/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Flex rtl render component should be rendered correctly in RTL direction 1`] = ` +
+
+ test1 +
+
+ test2 +
+
+`; diff --git a/components/flex/__tests__/demo-extend.test.ts b/components/flex/__tests__/demo-extend.test.ts new file mode 100644 index 0000000000..59f0f997b6 --- /dev/null +++ b/components/flex/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('flex'); diff --git a/components/flex/__tests__/demo.test.ts b/components/flex/__tests__/demo.test.ts new file mode 100644 index 0000000000..9f18ce06a3 --- /dev/null +++ b/components/flex/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('flex'); diff --git a/components/flex/__tests__/image.test.ts b/components/flex/__tests__/image.test.ts new file mode 100644 index 0000000000..3f8a7a1d0b --- /dev/null +++ b/components/flex/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('flex image', () => { + imageDemoTest('flex'); +}); diff --git a/components/flex/__tests__/index.test.tsx b/components/flex/__tests__/index.test.tsx new file mode 100644 index 0000000000..7ec72caa71 --- /dev/null +++ b/components/flex/__tests__/index.test.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import Flex from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { render } from '../../../tests/utils'; + +const FunCom = React.forwardRef((props, ref) => ( +
+ test FC +
+)); + +class ClassCom extends React.PureComponent<{ className?: string }> { + render() { + return
test Class
; + } +} + +describe('Flex', () => { + mountTest(() => ( + +
test1
+
test2
+
+ )); + rtlTest(() => ( + +
test1
+
test2
+
+ )); + it('Flex', () => { + const { container, rerender } = render(test); + expect(container.querySelector('.ant-flex')).toHaveStyle({ justifyContent: 'center' }); + rerender(test); + expect(container.querySelector('.ant-flex')).toHaveStyle({ flex: '0 1 auto' }); + rerender(test); + expect(container.querySelector('.ant-flex')).toHaveStyle({ gap: '100px' }); + }); + it('Component work', () => { + const testFcRef = React.createRef(); + const testClsRef = React.createRef(); + const { container, rerender } = render(test); + expect(container.querySelector('.ant-flex')?.tagName).toBe('DIV'); + rerender(test); + expect(container.querySelector('.ant-flex')?.tagName).toBe('SPAN'); + rerender( }>test); + expect(container.querySelector('.ant-flex')?.textContent).toBe('test FC'); + expect(testFcRef.current).toBeTruthy(); + rerender( }>test); + expect(container.querySelector('.ant-flex')?.textContent).toBe('test Class'); + expect(testClsRef.current).toBeTruthy(); + }); + + it('when vertical=true should stretch work', () => { + const { container, rerender } = render(test); + expect(container.querySelector('.ant-flex')).toHaveClass( + 'ant-flex-align-stretch', + ); + rerender( + + test + , + ); + expect(container.querySelector('.ant-flex')).toHaveClass( + 'ant-flex-align-center', + ); + }); +}); diff --git a/components/flex/demo/align.md b/components/flex/demo/align.md new file mode 100644 index 0000000000..b98b06ce02 --- /dev/null +++ b/components/flex/demo/align.md @@ -0,0 +1,7 @@ +## zh-CN + +设置对齐方式。 + +## en-US + +Set align. diff --git a/components/flex/demo/align.tsx b/components/flex/demo/align.tsx new file mode 100644 index 0000000000..ef6923f468 --- /dev/null +++ b/components/flex/demo/align.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button, Flex, Segmented } from 'antd'; +import type { FlexProps } from 'antd'; +import type { SegmentedProps } from 'antd/es/segmented'; + +const boxStyle: React.CSSProperties = { + width: '100%', + height: 120, + borderRadius: 6, + border: '1px solid #40a9ff', +}; + +const justifyOptions = [ + 'flex-start', + 'center', + 'flex-end', + 'space-between', + 'space-around', + 'space-evenly', +]; + +const alignOptions = ['flex-start', 'center', 'flex-end']; + +const App: React.FC = () => { + const [justify, setJustify] = React.useState(justifyOptions[0]); + const [alignItems, setAlignItems] = React.useState(alignOptions[0]); + return ( + +

Select justify :

+ +

Select align :

+ + + + + + + +
+ ); +}; + +export default App; diff --git a/components/flex/demo/basic.md b/components/flex/demo/basic.md new file mode 100644 index 0000000000..d8863c5bd7 --- /dev/null +++ b/components/flex/demo/basic.md @@ -0,0 +1,7 @@ +## zh-CN + +最简单的用法。 + +## en-US + +The basic usage. diff --git a/components/flex/demo/basic.tsx b/components/flex/demo/basic.tsx new file mode 100644 index 0000000000..fa6b183e6a --- /dev/null +++ b/components/flex/demo/basic.tsx @@ -0,0 +1,27 @@ +/* eslint-disable react/no-array-index-key */ +import React from 'react'; +import { Flex, Radio } from 'antd'; + +const baseStyle: React.CSSProperties = { + width: '25%', + height: 54, +}; + +const App: React.FC = () => { + const [value, setValue] = React.useState('horizontal'); + return ( + + setValue(e.target.value)}> + horizontal + vertical + + + {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} + + + ); +}; + +export default App; diff --git a/components/flex/demo/combination.md b/components/flex/demo/combination.md new file mode 100644 index 0000000000..cb5a860346 --- /dev/null +++ b/components/flex/demo/combination.md @@ -0,0 +1,7 @@ +## zh-CN + +嵌套使用,可以实现更复杂的布局。 + +## en-US + +Nesting can achieve more complex layouts. diff --git a/components/flex/demo/combination.tsx b/components/flex/demo/combination.tsx new file mode 100644 index 0000000000..05cc57f098 --- /dev/null +++ b/components/flex/demo/combination.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Button, Card, Flex, Typography } from 'antd'; + +const cardStyle: React.CSSProperties = { + width: 620, +}; + +const imgStyle: React.CSSProperties = { + display: 'block', + width: 273, +}; + +const App: React.FC = () => ( + + + avatar + + + “antd is an enterprise-class UI design language and React UI library.” + + + + + +); + +export default App; diff --git a/components/flex/demo/debug.md b/components/flex/demo/debug.md new file mode 100644 index 0000000000..22f1c21289 --- /dev/null +++ b/components/flex/demo/debug.md @@ -0,0 +1,7 @@ +## zh-CN + +调试专用。 + +## en-US + +Use for debug. diff --git a/components/flex/demo/debug.tsx b/components/flex/demo/debug.tsx new file mode 100644 index 0000000000..50f504005d --- /dev/null +++ b/components/flex/demo/debug.tsx @@ -0,0 +1,33 @@ +/* eslint-disable react/no-array-index-key */ +import React from 'react'; +import { Flex } from 'antd'; + +const App: React.FC = () => ( + <> + + {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} + + + {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} + + +); + +export default App; diff --git a/components/flex/demo/gap.md b/components/flex/demo/gap.md new file mode 100644 index 0000000000..3a8352de16 --- /dev/null +++ b/components/flex/demo/gap.md @@ -0,0 +1,7 @@ +## zh-CN + +使用 `gap` 设置元素之间的间距,预设了 `small`、`middle`、`large` 三种尺寸,也可以自定义间距。 + +## en-US + +Set the `gap` between elements, which has three preset sizes: `small`, `middle`, `large`, You can also customize the gap size. diff --git a/components/flex/demo/gap.tsx b/components/flex/demo/gap.tsx new file mode 100644 index 0000000000..1d80f1f76a --- /dev/null +++ b/components/flex/demo/gap.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Button, Flex, Radio, Slider } from 'antd'; +import type { SizeType } from 'antd/es/config-provider/SizeContext'; + +const App: React.FC = () => { + const [gapSize, setGapSize] = React.useState('small'); + const [customGapSize, setCustomGapSize] = React.useState(0); + return ( + + setGapSize(e.target.value)}> + {['small', 'middle', 'large', 'customize'].map((size) => ( + + {size} + + ))} + + {gapSize === 'customize' && } + + + + + + + + ); +}; + +export default App; diff --git a/components/flex/demo/wrap.md b/components/flex/demo/wrap.md new file mode 100644 index 0000000000..0a306a11c1 --- /dev/null +++ b/components/flex/demo/wrap.md @@ -0,0 +1,7 @@ +## zh-CN + +自动换行。 + +## en-US + +Auto wrap line. diff --git a/components/flex/demo/wrap.tsx b/components/flex/demo/wrap.tsx new file mode 100644 index 0000000000..13e9629c49 --- /dev/null +++ b/components/flex/demo/wrap.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { Button, Flex } from 'antd'; + +const Demo: React.FC = () => ( + + {Array.from({ length: 24 }, (_, i) => ( + + ))} + +); + +export default Demo; diff --git a/components/flex/index.en-US.md b/components/flex/index.en-US.md new file mode 100644 index 0000000000..c11c9e1a2f --- /dev/null +++ b/components/flex/index.en-US.md @@ -0,0 +1,45 @@ +--- +category: Components +group: Layout +title: Flex +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*SMzgSJZE_AwAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*8yArQ43EGccAAAAAAAAAAAAADrJ8AQ/original +tag: New +--- + +Flex. Available since `5.10.0`. + +## When To Use + +- Good for setting spacing between elements. +- Suitable for setting various horizontal and vertical alignments. + +## Examples + + +Basic +align +gap +Wrap +combination +debug + +## API + +> This component is available since `antd@5.10.0`. The default behavior of Flex in horizontal mode is to align upward, In vertical mode, aligns the stretch, You can adjust this via properties. + +Common props ref:[Common props](/docs/react/common-props) + +| Property | Description | type | Default | Version | +| --- | --- | --- | --- | --- | +| vertical | Is direction of the flex vertical, use `flex-direction: column` | boolean | `false` | | +| wrap | Set whether the element is displayed in a single line or in multiple lines | reference [flex-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap) | nowrap | | +| justify | Sets the alignment of elements in the direction of the main axis | reference [justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content) | normal | | +| align | Sets the alignment of elements in the direction of the cross axis | reference [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) | normal | | +| flex | flex CSS shorthand properties | reference [flex](https://developer.mozilla.org/en-US/docs/Web/CSS/flex) | normal | | +| gap | Sets the gap between grids | `small` \| `middle` \| `large` \| string \| number | - | | +| component | custom element type | React.ComponentType | `div` | | + +## Design Token + + diff --git a/components/flex/index.tsx b/components/flex/index.tsx new file mode 100644 index 0000000000..cebaca23e0 --- /dev/null +++ b/components/flex/index.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import classNames from 'classnames'; +import omit from 'rc-util/lib/omit'; + +import { isPresetSize } from '../_util/gapSize'; +import { ConfigContext } from '../config-provider'; +import type { ConfigConsumerProps } from '../config-provider'; +import type { FlexProps } from './interface'; +import useStyle from './style'; +import createFlexClassNames from './utils'; + +const Flex = React.forwardRef((props, ref) => { + const { + prefixCls: customizePrefixCls, + rootClassName, + className, + style, + flex, + gap, + children, + vertical = false, + component: Component = 'div', + ...othersProps + } = props; + + const { + flex: ctxFlex, + direction: ctxDirection, + getPrefixCls, + } = React.useContext(ConfigContext); + + const prefixCls = getPrefixCls('flex', customizePrefixCls); + + const [wrapSSR, hashId] = useStyle(prefixCls); + + const mergedVertical = vertical ?? ctxFlex?.vertical; + + const mergedCls = classNames( + className, + rootClassName, + ctxFlex?.className, + prefixCls, + hashId, + createFlexClassNames(prefixCls, props), + { + [`${prefixCls}-rtl`]: ctxDirection === 'rtl', + [`${prefixCls}-gap-${gap}`]: isPresetSize(gap), + [`${prefixCls}-vertical`]: mergedVertical, + }, + ); + + const mergedStyle: React.CSSProperties = { ...ctxFlex?.style, ...style }; + + if (flex) { + mergedStyle.flex = flex; + } + + if (gap && !isPresetSize(gap)) { + mergedStyle.gap = gap; + } + + return wrapSSR( + + {children} + , + ); +}); + +if (process.env.NODE_ENV !== 'production') { + Flex.displayName = 'Flex'; +} + +export default Flex; diff --git a/components/flex/index.zh-CN.md b/components/flex/index.zh-CN.md new file mode 100644 index 0000000000..8ca29d83ea --- /dev/null +++ b/components/flex/index.zh-CN.md @@ -0,0 +1,46 @@ +--- +category: Components +subtitle: 弹性布局 +group: 布局 +title: Flex +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*SMzgSJZE_AwAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*8yArQ43EGccAAAAAAAAAAAAADrJ8AQ/original +tag: New +--- + +弹性布局。自 `5.10.0` 版本开始提供该组件。 + +## 何时使用 + +- 适合设置元素之间的间距。 +- 适合设置各种水平、垂直对齐方式。 + +## 代码演示 + + +基本布局 +对齐方式 +设置间隙 +自动换行 +组合使用 +调试专用 + +## API + +> 自 `antd@5.10.0` 版本开始提供该组件。Flex 组件默认行为在水平模式下,为向上对齐,在垂直模式下,为拉伸对齐,你可以通过属性进行调整。 + +通用属性参考:[通用属性](/docs/react/common-props) + +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| vertical | flex 主轴的方向是否垂直,使用 `flex-direction: column` | boolean | `false` | +| wrap | 设置元素单行显示还是多行显示 | 参考 [flex-wrap](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-wrap) | nowrap | | +| justify | 设置元素在主轴方向上的对齐方式 | 参考 [justify-content](https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content) | normal | | +| align | 设置元素在交叉轴方向上的对齐方式 | 参考 [align-items](https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items) | normal | | +| flex | flex CSS 简写属性 | 参考 [flex](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex) | normal | | +| gap | 设置网格之间的间隙 | `small` \| `middle` \| `large` \| string \| number | - | | +| component | 自定义元素类型 | React.ComponentType | `div` | | + +## Design Token + + diff --git a/components/flex/interface.ts b/components/flex/interface.ts new file mode 100644 index 0000000000..d5240cfcae --- /dev/null +++ b/components/flex/interface.ts @@ -0,0 +1,17 @@ +import type React from 'react'; + +import type { AnyObject } from '../_util/type'; +import type { SizeType } from '../config-provider/SizeContext'; + +export interface FlexProps

extends React.HTMLAttributes { + prefixCls?: string; + rootClassName?: string; + vertical?: boolean; + wrap?: React.CSSProperties['flexWrap']; + justify?: React.CSSProperties['justifyContent']; + align?: React.CSSProperties['alignItems']; + flex?: React.CSSProperties['flex']; + gap?: React.CSSProperties['gap'] | SizeType; + children: React.ReactNode; + component?: React.ComponentType

| string; +} diff --git a/components/flex/style/index.ts b/components/flex/style/index.ts new file mode 100644 index 0000000000..d90e646578 --- /dev/null +++ b/components/flex/style/index.ts @@ -0,0 +1,111 @@ +import type { CSSInterpolation } from '@ant-design/cssinjs'; + +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook, mergeToken } from '../../theme/internal'; +import { alignItemsValues, flexWrapValues, justifyContentValues } from '../utils'; + +/** Component only token. Which will handle additional calculation of alias token */ +export interface ComponentToken { + // Component token here +} + +export interface FlexToken extends FullToken<'Flex'> { + /** + * @nameZH 小间隙 + * @nameEN Small Gap + * @desc 控制元素的小间隙。 + * @descEN Control the small gap of the element. + */ + flexGapSM: number; + /** + * @nameZH 间隙 + * @nameEN Gap + * @desc 控制元素的间隙。 + * @descEN Control the gap of the element. + */ + flexGap: number; + /** + * @nameZH 大间隙 + * @nameEN Large Gap + * @desc 控制元素的大间隙。 + * @descEN Control the large gap of the element. + */ + flexGapLG: number; +} + +const genFlexStyle: GenerateStyle = (token) => { + const { componentCls } = token; + return { + [componentCls]: { + display: 'flex', + '&-vertical': { + flexDirection: 'column', + }, + '&-rtl': { + direction: 'rtl', + }, + '&:empty': { + display: 'none', + }, + }, + }; +}; + +const genFlexGapStyle: GenerateStyle = (token) => { + const { componentCls } = token; + return { + [componentCls]: { + '&-gap-small': { + gap: token.flexGapSM, + }, + '&-gap-middle': { + gap: token.flexGap, + }, + '&-gap-large': { + gap: token.flexGapLG, + }, + }, + }; +}; + +const genFlexWrapStyle: GenerateStyle = (token) => { + const { componentCls } = token; + const wrapStyle: CSSInterpolation = {}; + flexWrapValues.forEach((value) => { + wrapStyle[`${componentCls}-wrap-${value}`] = { flexWrap: value }; + }); + return wrapStyle; +}; + +const genAlignItemsStyle: GenerateStyle = (token) => { + const { componentCls } = token; + const alignStyle: CSSInterpolation = {}; + alignItemsValues.forEach((value) => { + alignStyle[`${componentCls}-align-${value}`] = { alignItems: value }; + }); + return alignStyle; +}; + +const genJustifyContentStyle: GenerateStyle = (token) => { + const { componentCls } = token; + const justifyStyle: CSSInterpolation = {}; + justifyContentValues.forEach((value) => { + justifyStyle[`${componentCls}-justify-${value}`] = { justifyContent: value }; + }); + return justifyStyle; +}; + +export default genComponentStyleHook<'Flex'>('Flex', (token) => { + const flexToken = mergeToken(token, { + flexGapSM: token.paddingXS, + flexGap: token.padding, + flexGapLG: token.paddingLG, + }); + return [ + genFlexStyle(flexToken), + genFlexGapStyle(flexToken), + genFlexWrapStyle(flexToken), + genAlignItemsStyle(flexToken), + genJustifyContentStyle(flexToken), + ]; +}); diff --git a/components/flex/utils.ts b/components/flex/utils.ts new file mode 100644 index 0000000000..1cd814e697 --- /dev/null +++ b/components/flex/utils.ts @@ -0,0 +1,68 @@ +import classNames from 'classnames'; + +import type { FlexProps } from './interface'; + +export const flexWrapValues = ['wrap', 'nowrap', 'wrap-reverse'] as const; + +export const justifyContentValues = [ + 'flex-start', + 'flex-end', + 'start', + 'end', + 'center', + 'space-between', + 'space-around', + 'space-evenly', + 'stretch', + 'normal', + 'left', + 'right', +] as const; + +export const alignItemsValues = [ + 'center', + 'start', + 'end', + 'flex-start', + 'flex-end', + 'self-start', + 'self-end', + 'baseline', + 'normal', + 'stretch', +] as const; + +const genClsWrap = (prefixCls: string, props: FlexProps) => { + const wrapCls: Record = {}; + flexWrapValues.forEach((cssKey) => { + wrapCls[`${prefixCls}-wrap-${cssKey}`] = props.wrap === cssKey; + }); + return wrapCls; +}; + +const genClsAlign = (prefixCls: string, props: FlexProps) => { + const alignCls: Record = {}; + alignItemsValues.forEach((cssKey) => { + alignCls[`${prefixCls}-align-${cssKey}`] = props.align === cssKey; + }); + alignCls[`${prefixCls}-align-stretch`] = !props.align && !!props.vertical; + return alignCls; +}; + +const genClsJustify = (prefixCls: string, props: FlexProps) => { + const justifyCls: Record = {}; + justifyContentValues.forEach((cssKey) => { + justifyCls[`${prefixCls}-justify-${cssKey}`] = props.justify === cssKey; + }); + return justifyCls; +}; + +function createFlexClassNames(prefixCls: string, props: FlexProps) { + return classNames({ + ...genClsWrap(prefixCls, props), + ...genClsAlign(prefixCls, props), + ...genClsJustify(prefixCls, props), + }); +} + +export default createFlexClassNames; diff --git a/components/grid/__tests__/server.test.tsx b/components/grid/__tests__/server.test.tsx index 528919f4df..a223446e9f 100644 --- a/components/grid/__tests__/server.test.tsx +++ b/components/grid/__tests__/server.test.tsx @@ -13,10 +13,10 @@ describe('Grid.Server', () => { , ); - expect((container.querySelector('.ant-row') as HTMLElement)?.style.marginLeft).toBe('-4px'); - expect((container.querySelector('.ant-row') as HTMLElement)?.style.marginRight).toBe('-4px'); - expect((container.querySelector('.ant-row') as HTMLElement)?.style.marginTop).toBe(''); - expect((container.querySelector('.ant-row') as HTMLElement)?.style.marginBottom).toBe(''); + expect(container.querySelector('.ant-row')?.style.marginLeft).toBe('-4px'); + expect(container.querySelector('.ant-row')?.style.marginRight).toBe('-4px'); + expect(container.querySelector('.ant-row')?.style.marginTop).toBe(''); + expect(container.querySelector('.ant-row')?.style.marginBottom).toBe(''); expect((container.querySelector('.ant-col') as HTMLElement)?.style.paddingLeft).toBe('4px'); expect((container.querySelector('.ant-col') as HTMLElement)?.style.paddingRight).toBe('4px'); diff --git a/components/index.ts b/components/index.ts index c88e98b25b..0d459f3275 100644 --- a/components/index.ts +++ b/components/index.ts @@ -54,6 +54,8 @@ export type { } from './dropdown'; export { default as Empty } from './empty'; export type { EmptyProps } from './empty'; +export { default as Flex } from './flex'; +export type { FlexProps } from './flex/interface'; export { default as FloatButton } from './float-button'; export type { FloatButtonGroupProps, FloatButtonProps } from './float-button/interface'; export { default as Form } from './form'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 296efdfa00..fa0b42de32 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -20,10 +20,11 @@ import type { ComponentToken as DividerComponentToken } from '../../divider/styl import type { ComponentToken as DrawerComponentToken } from '../../drawer/style'; import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style'; import type { ComponentToken as EmptyComponentToken } from '../../empty/style'; +import type { ComponentToken as FlexComponentToken } from '../../flex/style'; import type { ComponentToken as FloatButtonComponentToken } from '../../float-button/style'; import type { ComponentToken as ImageComponentToken } from '../../image/style'; -import type { ComponentToken as InputComponentToken } from '../../input/style'; import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style'; +import type { ComponentToken as InputComponentToken } from '../../input/style'; import type { ComponentToken as LayoutComponentToken } from '../../layout/style'; import type { ComponentToken as ListComponentToken } from '../../list/style'; import type { ComponentToken as MentionsComponentToken } from '../../mentions/style'; @@ -55,8 +56,8 @@ import type { ComponentToken as TimelineComponentToken } from '../../timeline/st import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style'; import type { ComponentToken as TourComponentToken } from '../../tour/style'; import type { ComponentToken as TransferComponentToken } from '../../transfer/style'; -import type { ComponentToken as TreeComponentToken } from '../../tree/style'; import type { ComponentToken as TreeSelectComponentToken } from '../../tree-select/style'; +import type { ComponentToken as TreeComponentToken } from '../../tree/style'; import type { ComponentToken as TypographyComponentToken } from '../../typography/style'; import type { ComponentToken as UploadComponentToken } from '../../upload/style'; import type { ComponentToken as FormComponentToken } from '../../form/style'; @@ -82,6 +83,7 @@ export interface ComponentTokenMap { Drawer?: DrawerComponentToken; Dropdown?: DropdownComponentToken; Empty?: EmptyComponentToken; + Flex?: FlexComponentToken; FloatButton?: FloatButtonComponentToken; Form?: FormComponentToken; Grid?: {}; diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index f5ad146a56..0a4758fc7e 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -27,6 +27,7 @@ exports[`antd dist files exports modules correctly 1`] = ` "Drawer", "Dropdown", "Empty", + "Flex", "FloatButton", "Form", "Grid",