Skip to content

Commit a5029cb

Browse files
committed
feat(flex): add Flex component for lightweight flexbox layouts
Uses CSS gap directly on the container with no child wrapping. Supports vertical, wrap, justify, align, gap (sm/md/lg or custom), flex shorthand, and custom element types via component prop. Includes interactive demos, Chinese docs, and site registration.
1 parent 119c727 commit a5029cb

13 files changed

Lines changed: 272 additions & 0 deletions

File tree

components/flex/demo/align.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<demo>
2+
3+
### Alignment
4+
5+
Set `justify` and `align` to control alignment of items.
6+
7+
```jsx live
8+
() => {
9+
const justifyOptions = ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'];
10+
const alignOptions = ['flex-start', 'center', 'flex-end'];
11+
12+
const [justify, setJustify] = React.useState('flex-start');
13+
const [align, setAlign] = React.useState('flex-start');
14+
15+
const boxStyle = {
16+
width: '100%',
17+
height: 120,
18+
borderRadius: 6,
19+
border: '1px solid #6e41bf',
20+
};
21+
22+
return (
23+
<Flex gap="md" align="flex-start" vertical>
24+
<span>Select justify:</span>
25+
<Segmented options={justifyOptions} value={justify} onChange={val => setJustify(val)} />
26+
<span>Select align:</span>
27+
<Segmented options={alignOptions} value={align} onChange={val => setAlign(val)} />
28+
<Flex style={boxStyle} justify={justify} align={align}>
29+
<Button btnType="primary">Primary</Button>
30+
<Button btnType="primary">Primary</Button>
31+
<Button btnType="primary">Primary</Button>
32+
<Button btnType="primary">Primary</Button>
33+
</Flex>
34+
</Flex>
35+
);
36+
}
37+
```
38+
39+
</demo>

components/flex/demo/basic.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<demo>
2+
3+
### Basic Layout
4+
5+
Use `vertical` to control the direction. Default is horizontal.
6+
7+
```jsx live
8+
() => {
9+
const [vertical, setVertical] = React.useState(false);
10+
11+
const baseStyle = {
12+
width: '25%',
13+
height: 54,
14+
borderRadius: 6,
15+
};
16+
17+
return (
18+
<Flex gap="md" vertical>
19+
<Radio.Group value={vertical ? 'vertical' : 'horizontal'} onChange={val => setVertical(val === 'vertical')}>
20+
<Radio value="horizontal">horizontal</Radio>
21+
<Radio value="vertical">vertical</Radio>
22+
</Radio.Group>
23+
<Flex vertical={vertical}>
24+
{Array.from({ length: 4 }).map((_, i) => (
25+
<div key={i} style={{ ...baseStyle, backgroundColor: i % 2 ? '#6e41bf' : 'rgba(110, 65, 191, 0.75)' }} />
26+
))}
27+
</Flex>
28+
</Flex>
29+
);
30+
}
31+
```
32+
33+
</demo>

components/flex/demo/gap.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<demo>
2+
3+
### Gap
4+
5+
Preset sizes `sm` (8px), `md` (16px), `lg` (24px) or a custom number via slider.
6+
7+
```jsx live
8+
() => {
9+
const [gapSize, setGapSize] = React.useState('sm');
10+
const [customGapSize, setCustomGapSize] = React.useState(0);
11+
12+
return (
13+
<Flex gap="md" vertical>
14+
<Radio.Group value={gapSize} onChange={val => setGapSize(val)}>
15+
<Radio value="sm">small</Radio>
16+
<Radio value="md">medium</Radio>
17+
<Radio value="lg">large</Radio>
18+
<Radio value="custom">custom</Radio>
19+
</Radio.Group>
20+
{gapSize === 'custom' && <Slider value={customGapSize} onChange={val => setCustomGapSize(val)} />}
21+
<Flex gap={gapSize !== 'custom' ? gapSize : customGapSize}>
22+
<Button btnType="primary">Primary</Button>
23+
<Button>Default</Button>
24+
<Button btnType="outline">Outline</Button>
25+
<Button btnType="link">Link</Button>
26+
</Flex>
27+
</Flex>
28+
);
29+
}
30+
```
31+
32+
</demo>

components/flex/demo/wrap.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<demo>
2+
3+
### Wrap
4+
5+
Flex items wrap automatically when `wrap` is set.
6+
7+
```jsx live
8+
() => (
9+
<Flex wrap="wrap" gap="sm">
10+
{Array.from({ length: 24 }, (_, i) => (
11+
<Button key={i} btnType="primary">Button</Button>
12+
))}
13+
</Flex>
14+
)
15+
```
16+
17+
</demo>

components/flex/index.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Basic from './demo/basic.md'
2+
import Align from './demo/align.md'
3+
import Gap from './demo/gap.md'
4+
import Wrap from './demo/wrap.md'
5+
6+
# Flex
7+
8+
A flexbox container component using CSS `gap` for spacing with no child wrapping.
9+
10+
## Scenario
11+
12+
Use Flex when you need a lightweight flexbox layout without wrapping each child in additional elements.
13+
14+
## Usage
15+
16+
```jsx
17+
import { Flex } from 'tiny-ui';
18+
```
19+
20+
## Examples
21+
22+
<Basic />
23+
<Align />
24+
<Gap />
25+
<Wrap />
26+
27+
## API
28+
29+
| Property | Description | Type | Default |
30+
| --------- | -------------------------------- | -------------------------------------------------------------------- | ------- |
31+
| vertical | Flex direction column | `boolean` | `false` |
32+
| wrap | CSS flex-wrap | `nowrap` &#124; `wrap` &#124; `wrap-reverse` | - |
33+
| justify | CSS justify-content | `string` | - |
34+
| align | CSS align-items | `string` | - |
35+
| gap | Gap between items | `sm` &#124; `md` &#124; `lg` &#124; `number` &#124; `string` | - |
36+
| flex | CSS flex shorthand | `string` | - |
37+
| component | Custom element type | `React.ElementType` | `div` |

components/flex/index.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, { useContext } from 'react';
2+
import classNames from 'classnames';
3+
import { ConfigContext } from '../config-provider/config-context';
4+
import { getPrefixCls } from '../_utils/general';
5+
import { SizeType } from '../_utils/props';
6+
import { FlexProps } from './types';
7+
8+
const gapSizes: Record<SizeType, number> = {
9+
sm: 8,
10+
md: 16,
11+
lg: 24,
12+
};
13+
14+
const Flex = React.forwardRef<HTMLElement, FlexProps>(
15+
(props: FlexProps, ref): JSX.Element => {
16+
const {
17+
vertical = false,
18+
wrap,
19+
justify,
20+
align,
21+
gap,
22+
flex,
23+
component: Component = 'div',
24+
className,
25+
style,
26+
children,
27+
prefixCls: customisedCls,
28+
...otherProps
29+
} = props;
30+
const configContext = useContext(ConfigContext);
31+
const prefixCls = getPrefixCls('flex', configContext.prefixCls, customisedCls);
32+
33+
const cls = classNames(prefixCls, className);
34+
35+
const mergedStyle: React.CSSProperties = {
36+
flexDirection: vertical ? 'column' : undefined,
37+
flexWrap: wrap,
38+
justifyContent: justify,
39+
alignItems: align,
40+
gap: typeof gap === 'string' && gap in gapSizes ? gapSizes[gap as SizeType] : gap,
41+
flex,
42+
...style,
43+
};
44+
45+
return (
46+
<Component {...otherProps} ref={ref} className={cls} style={mergedStyle}>
47+
{children}
48+
</Component>
49+
);
50+
}
51+
);
52+
53+
Flex.displayName = 'Flex';
54+
55+
export default Flex;

components/flex/index.zh_CN.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Basic from './demo/basic.md'
2+
import Align from './demo/align.md'
3+
import Gap from './demo/gap.md'
4+
import Wrap from './demo/wrap.md'
5+
6+
# Flex
7+
8+
弹性盒子布局容器,使用 CSS `gap` 设置间距,无需额外包裹子元素。
9+
10+
## 使用场景
11+
12+
需要轻量级弹性布局时使用,无需为每个子元素添加额外的包裹元素。
13+
14+
## 引入方式
15+
16+
```jsx
17+
import { Flex } from 'tiny-ui';
18+
```
19+
20+
## 代码示例
21+
22+
<Basic />
23+
<Align />
24+
<Gap />
25+
<Wrap />
26+
27+
## API
28+
29+
| 属性 | 说明 | 类型 | 默认值 |
30+
| --------- | ----------------------------- | -------------------------------------------------------------------- | ------- |
31+
| vertical | 设置为纵向排列 | `boolean` | `false` |
32+
| wrap | CSS flex-wrap 属性 | `nowrap` &#124; `wrap` &#124; `wrap-reverse` | - |
33+
| justify | CSS justify-content 属性 | `string` | - |
34+
| align | CSS align-items 属性 | `string` | - |
35+
| gap | 子元素间距 | `sm` &#124; `md` &#124; `lg` &#124; `number` &#124; `string` | - |
36+
| flex | CSS flex 简写属性 | `string` | - |
37+
| component | 自定义元素类型 | `React.ElementType` | `div` |

components/flex/style/_index.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@use '../../style/variables' as *;
2+
3+
.#{$prefix}-flex {
4+
display: flex;
5+
}

components/flex/style/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './index.scss';

components/flex/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import { BaseProps, SizeType } from '../_utils/props';
3+
4+
export interface FlexProps extends BaseProps, React.PropsWithRef<JSX.IntrinsicElements['div']> {
5+
vertical?: boolean;
6+
wrap?: React.CSSProperties['flexWrap'];
7+
justify?: React.CSSProperties['justifyContent'];
8+
align?: React.CSSProperties['alignItems'];
9+
gap?: SizeType | React.CSSProperties['gap'];
10+
flex?: React.CSSProperties['flex'];
11+
component?: React.ElementType;
12+
}

0 commit comments

Comments
 (0)