Browse Source
* feat(Flex): New component * fix: Unified type * test: Add unit test and update snapshots * docs: update md file --------- Co-authored-by: undefined <undefined>pull/7102/head
selicens
1 year ago
committed by
GitHub
20 changed files with 803 additions and 0 deletions
@ -0,0 +1,13 @@
|
||||
import type { SizeType } from '../config-provider/SizeContext'; |
||||
|
||||
export function isPresetSize(size?: SizeType | string | number): size is SizeType { |
||||
return ['small', 'middle', 'large'].includes(size as string); |
||||
} |
||||
|
||||
export function isValidGapNumber(size?: SizeType | string | number): 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); |
||||
} |
@ -0,0 +1,146 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`renders ./components/flex/demo/align.vue correctly 1`] = ` |
||||
<div class="ant-flex ant-flex-align-start ant-flex-gap-middle ant-flex-vertical"> |
||||
<p>Select justify :</p> |
||||
<div class="ant-segmented "> |
||||
<div class="ant-segmented-group"> |
||||
<!----><label class="ant-segmented-item ant-segmented-item-selected"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="flex-start">flex-start</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="center">center</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="flex-end">flex-end</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="space-between">space-between</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="space-around">space-around</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="space-evenly">space-evenly</div> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<p>Select align :</p> |
||||
<div class="ant-segmented "> |
||||
<div class="ant-segmented-group"> |
||||
<!----><label class="ant-segmented-item ant-segmented-item-selected"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="flex-start">flex-start</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="center">center</div> |
||||
</label><label class="ant-segmented-item"><input class="ant-segmented-item-input" type="radio"> |
||||
<div class="ant-segmented-item-label" title="flex-end">flex-end</div> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="ant-flex ant-flex-align-flex-start ant-flex-justify-flex-start" style="width: 100%; height: 120px; border-radius: 6px; border: 1px solid #40a9ff;"><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Primary</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Primary</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Primary</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Primary</span> |
||||
</button></div> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`renders ./components/flex/demo/basic.vue correctly 1`] = ` |
||||
<div class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"> |
||||
<div class="ant-radio-group ant-radio-group-outline"><label class="ant-radio-wrapper ant-radio-wrapper-checked"><span class="ant-radio ant-radio-checked"><input type="radio" class="ant-radio-input" value="horizontal"><span class="ant-radio-inner"></span></span><span>horizontal</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="vertical"><span class="ant-radio-inner"></span></span><span>vertical</span></label></div> |
||||
<div class="ant-flex"> |
||||
<div style="width: 25%; height: 54px; background: rgba(22, 119, 255, 0.749);"></div> |
||||
<div style="width: 25%; height: 54px; background: rgb(22, 119, 255);"></div> |
||||
<div style="width: 25%; height: 54px; background: rgba(22, 119, 255, 0.749);"></div> |
||||
<div style="width: 25%; height: 54px; background: rgb(22, 119, 255);"></div> |
||||
</div> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`renders ./components/flex/demo/combination.vue correctly 1`] = ` |
||||
<div style="width: 620px;" class="ant-card ant-card-bordered"> |
||||
<!----> |
||||
<!----> |
||||
<div class="ant-card-body" style="padding: 0px; overflow: hidden;"> |
||||
<div class="ant-flex ant-flex-justify-space-between"><img alt="avatar" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" style="display: block; width: 273px;"> |
||||
<div class="ant-flex ant-flex-align-flex-end ant-flex-justify-space-between ant-flex-vertical" style="padding: 32px;"> |
||||
<article class="ant-typography"> |
||||
<h3 class="ant-typography"> “antd is an enterprise-class UI design language and Vue UI library.” |
||||
<!----> |
||||
</h3> |
||||
</article><a class="ant-btn ant-btn-primary" href="https://antdv.com" target="_blank"> |
||||
<!----><span>Get Start</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!----> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`renders ./components/flex/demo/gap.vue correctly 1`] = ` |
||||
<div class="ant-flex ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"> |
||||
<div class="ant-radio-group ant-radio-group-outline"><label class="ant-radio-wrapper ant-radio-wrapper-checked"><span class="ant-radio ant-radio-checked"><input type="radio" class="ant-radio-input" value="small"><span class="ant-radio-inner"></span></span><span>small</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="middle"><span class="ant-radio-inner"></span></span><span>middle</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="large"><span class="ant-radio-inner"></span></span><span>large</span></label><label class="ant-radio-wrapper"><span class="ant-radio"><input type="radio" class="ant-radio-input" value="customize"><span class="ant-radio-inner"></span></span><span>customize</span></label></div> |
||||
<!--v-if--> |
||||
<div class="ant-flex ant-flex-gap-small"><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Primary</span> |
||||
</button><button class="ant-btn ant-btn-default" type="button"> |
||||
<!----><span>Default</span> |
||||
</button><button class="ant-btn ant-btn-dashed" type="button"> |
||||
<!----><span>Dashed</span> |
||||
</button><button class="ant-btn ant-btn-link" type="button"> |
||||
<!----><span>Link</span> |
||||
</button></div> |
||||
</div> |
||||
`; |
||||
|
||||
exports[`renders ./components/flex/demo/wrap.vue correctly 1`] = ` |
||||
<div class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small"><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button><button class="ant-btn ant-btn-primary" type="button"> |
||||
<!----><span>Button</span> |
||||
</button></div> |
||||
`; |
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest'; |
||||
|
||||
demoTest('flex'); |
@ -0,0 +1,58 @@
|
||||
import { mount } from '@vue/test-utils'; |
||||
import Flex from '..'; |
||||
import mountTest from '../../../tests/shared/mountTest'; |
||||
|
||||
describe('Flex', () => { |
||||
mountTest(Flex); |
||||
|
||||
it('Flex', () => { |
||||
const wrapper = mount({ |
||||
render() { |
||||
return <Flex justify="center">test</Flex>; |
||||
}, |
||||
}); |
||||
const wrapper2 = mount({ |
||||
render() { |
||||
return <Flex gap={100}>test</Flex>; |
||||
}, |
||||
}); |
||||
expect(wrapper.classes('ant-flex')).toBeTruthy(); |
||||
expect(wrapper.find('.ant-flex-justify-center')).toBeTruthy(); |
||||
expect(wrapper2.classes('ant-flex')).toBeTruthy(); |
||||
expect(wrapper2.element.style.gap).toBe('100px'); |
||||
}); |
||||
|
||||
it('Component work', () => { |
||||
const wrapper = mount({ |
||||
render() { |
||||
return <Flex>test</Flex>; |
||||
}, |
||||
}); |
||||
const wrapper2 = mount({ |
||||
render() { |
||||
return <Flex component="span">test</Flex>; |
||||
}, |
||||
}); |
||||
expect(wrapper.find('.ant-flex').element.tagName).toBe('DIV'); |
||||
expect(wrapper2.find('.ant-flex').element.tagName).toBe('SPAN'); |
||||
}); |
||||
|
||||
it('when vertical=true should stretch work', () => { |
||||
const wrapper = mount({ |
||||
render() { |
||||
return <Flex vertical>test</Flex>; |
||||
}, |
||||
}); |
||||
const wrapper2 = mount({ |
||||
render() { |
||||
return ( |
||||
<Flex vertical align="center"> |
||||
test |
||||
</Flex> |
||||
); |
||||
}, |
||||
}); |
||||
expect(wrapper.find('.ant-flex-align-stretch')).toBeTruthy(); |
||||
expect(wrapper2.find('.ant-flex-align-center')).toBeTruthy(); |
||||
}); |
||||
}); |
@ -0,0 +1,56 @@
|
||||
<docs> |
||||
--- |
||||
order: 1 |
||||
title: |
||||
zh-CN: 对齐方式 |
||||
en-US: Align |
||||
--- |
||||
|
||||
## zh-CN |
||||
|
||||
设置对齐方式。 |
||||
|
||||
## en-US |
||||
|
||||
Set align. |
||||
|
||||
</docs> |
||||
|
||||
<template> |
||||
<a-flex gap="middle" align="start" vertical> |
||||
<p>Select justify :</p> |
||||
<a-segmented v-model:value="justify" :options="justifyOptions" /> |
||||
<p>Select align :</p> |
||||
<a-segmented v-model:value="alignItems" :options="alignOptions" /> |
||||
<a-flex :style="{ ...boxStyle }" :justify="justify" :align="alignItems"> |
||||
<a-button type="primary">Primary</a-button> |
||||
<a-button type="primary">Primary</a-button> |
||||
<a-button type="primary">Primary</a-button> |
||||
<a-button type="primary">Primary</a-button> |
||||
</a-flex> |
||||
</a-flex> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { reactive, ref } from 'vue'; |
||||
import type { CSSProperties } from 'vue'; |
||||
import type { FlexProps } from 'ant-design-vue'; |
||||
const justifyOptions = reactive<FlexProps['justify'][]>([ |
||||
'flex-start', |
||||
'center', |
||||
'flex-end', |
||||
'space-between', |
||||
'space-around', |
||||
'space-evenly', |
||||
]); |
||||
|
||||
const alignOptions = reactive<FlexProps['align'][]>(['flex-start', 'center', 'flex-end']); |
||||
const justify = ref(justifyOptions[0]); |
||||
const alignItems = ref(alignOptions[0]); |
||||
const boxStyle: CSSProperties = { |
||||
width: '100%', |
||||
height: '120px', |
||||
borderRadius: '6px', |
||||
border: '1px solid #40a9ff', |
||||
}; |
||||
</script> |
@ -0,0 +1,43 @@
|
||||
<docs> |
||||
--- |
||||
order: 0 |
||||
title: |
||||
zh-CN: 基本布局 |
||||
en-US: Basic |
||||
--- |
||||
|
||||
## zh-CN |
||||
|
||||
最简单的用法。 |
||||
|
||||
## en-US |
||||
|
||||
The basic usage. |
||||
|
||||
</docs> |
||||
|
||||
<template> |
||||
<a-flex gap="middle" vertical> |
||||
<a-radio-group v-model:value="value"> |
||||
<a-radio value="horizontal">horizontal</a-radio> |
||||
<a-radio value="vertical">vertical</a-radio> |
||||
</a-radio-group> |
||||
<a-flex :vertical="value === 'vertical'"> |
||||
<div |
||||
v-for="(item, index) in new Array(4)" |
||||
:key="item" |
||||
:style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }" |
||||
/> |
||||
</a-flex> |
||||
</a-flex> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { ref } from 'vue'; |
||||
import type { CSSProperties } from 'vue'; |
||||
const value = ref('horizontal'); |
||||
const baseStyle: CSSProperties = { |
||||
width: '25%', |
||||
height: '54px', |
||||
}; |
||||
</script> |
@ -0,0 +1,48 @@
|
||||
<docs> |
||||
--- |
||||
order: 4 |
||||
title: |
||||
zh-CN: 组合使用 |
||||
en-US: combination |
||||
--- |
||||
|
||||
## zh-CN |
||||
|
||||
嵌套使用,可以实现更复杂的布局。 |
||||
|
||||
## en-US |
||||
|
||||
Nesting can achieve more complex layouts. |
||||
|
||||
</docs> |
||||
|
||||
<template> |
||||
<a-card :style="cardStyle" :body-style="{ padding: 0, overflow: 'hidden' }"> |
||||
<a-flex justify="space-between"> |
||||
<img |
||||
alt="avatar" |
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" |
||||
:style="imgStyle" |
||||
/> |
||||
<a-flex vertical align="flex-end" justify="space-between" :style="{ padding: '32px' }"> |
||||
<a-typography> |
||||
<a-typography-title :level="3"> |
||||
“antd is an enterprise-class UI design language and Vue UI library.” |
||||
</a-typography-title> |
||||
</a-typography> |
||||
<a-button type="primary" href="https://antdv.com" target="_blank">Get Start</a-button> |
||||
</a-flex> |
||||
</a-flex> |
||||
</a-card> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import type { CSSProperties } from 'vue'; |
||||
const cardStyle: CSSProperties = { |
||||
width: '620px', |
||||
}; |
||||
const imgStyle: CSSProperties = { |
||||
display: 'block', |
||||
width: '273px', |
||||
}; |
||||
</script> |
@ -0,0 +1,47 @@
|
||||
<docs> |
||||
--- |
||||
order: 2 |
||||
title: |
||||
zh-CN: 设置间隙 |
||||
en-US: gap |
||||
--- |
||||
|
||||
## 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. |
||||
|
||||
</docs> |
||||
|
||||
<template> |
||||
<a-flex gap="middle" vertical> |
||||
<a-radio-group v-model:value="gapSize"> |
||||
<a-radio value="small">small</a-radio> |
||||
<a-radio value="middle">middle</a-radio> |
||||
<a-radio value="large">large</a-radio> |
||||
<a-radio value="customize">customize</a-radio> |
||||
</a-radio-group> |
||||
<template v-if="gapSize === 'customize'"> |
||||
<a-slider v-model:value="customGapSize" /> |
||||
</template> |
||||
<a-flex :gap="gapSize !== 'customize' ? gapSize : customGapSize"> |
||||
<a-button type="primary">Primary</a-button> |
||||
<a-button>Default</a-button> |
||||
<a-button type="dashed">Dashed</a-button> |
||||
<a-button type="link">Link</a-button> |
||||
</a-flex> |
||||
</a-flex> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { ref } from 'vue'; |
||||
|
||||
type SizeType = 'small' | 'middle' | 'large' | undefined; |
||||
|
||||
const gapSize = ref<SizeType | 'customize'>('small'); |
||||
|
||||
const customGapSize = ref<number>(0); |
||||
</script> |
@ -0,0 +1,30 @@
|
||||
<template> |
||||
<demo-sort :cols="1"> |
||||
<Basic /> |
||||
<Align /> |
||||
<Gap /> |
||||
<Wrap /> |
||||
<Combination /> |
||||
</demo-sort> |
||||
</template> |
||||
<script lang="ts"> |
||||
import Align from './align.vue'; |
||||
import Basic from './basic.vue'; |
||||
import Combination from './combination.vue'; |
||||
import Gap from './gap.vue'; |
||||
import Wrap from './wrap.vue'; |
||||
import CN from '../index.zh-CN.md'; |
||||
import US from '../index.en-US.md'; |
||||
import { defineComponent } from 'vue'; |
||||
export default defineComponent({ |
||||
CN, |
||||
US, |
||||
components: { |
||||
Align, |
||||
Basic, |
||||
Combination, |
||||
Gap, |
||||
Wrap, |
||||
}, |
||||
}); |
||||
</script> |
@ -0,0 +1,23 @@
|
||||
<docs> |
||||
--- |
||||
order: 3 |
||||
title: |
||||
zh-CN: 自动换行 |
||||
en-US: Wrap |
||||
--- |
||||
|
||||
## zh-CN |
||||
|
||||
自动换行。 |
||||
|
||||
## en-US |
||||
|
||||
Auto wrap line. |
||||
|
||||
</docs> |
||||
|
||||
<template> |
||||
<a-flex wrap="wrap" gap="small"> |
||||
<a-button v-for="item in new Array(24)" :key="item" type="primary">Button</a-button> |
||||
</a-flex> |
||||
</template> |
@ -0,0 +1,32 @@
|
||||
--- |
||||
category: Components |
||||
type: 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 |
||||
--- |
||||
|
||||
## When To Use |
||||
|
||||
- Good for setting spacing between elements. |
||||
- Suitable for setting various horizontal and vertical alignments. |
||||
|
||||
### Difference with Space component |
||||
|
||||
- Space is used to set the spacing between inline elements. It will add a wrapper element for each child element for inline alignment. Suitable for equidistant arrangement of multiple child elements in rows and columns. |
||||
- Flex is used to set the layout of block-level elements. It does not add a wrapper element. Suitable for layout of child elements in vertical or horizontal direction, and provides more flexibility and control. |
||||
|
||||
## API |
||||
|
||||
> This component is available since `ant-design-vue@4.0.7`. The default behavior of Flex in horizontal mode is to align upward, In vertical mode, aligns the stretch, You can adjust this via properties. |
||||
|
||||
| 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 | Component | `div` | | |
@ -0,0 +1,66 @@
|
||||
import { defineComponent, shallowRef } from 'vue'; |
||||
import type { CSSProperties } from 'vue'; |
||||
import { useConfigContextInject } from '../config-provider/context'; |
||||
import useConfigInject from '../config-provider/hooks/useConfigInject'; |
||||
import useStyle from './style'; |
||||
import classNames from '../_util/classNames'; |
||||
import { isPresetSize } from '../_util/gapSize'; |
||||
import omit from '../_util/omit'; |
||||
import { withInstall } from '../_util/type'; |
||||
import type { FlexProps } from './interface'; |
||||
import { flexProps } from './interface'; |
||||
import createFlexClassNames from './utils'; |
||||
|
||||
const AFlex = defineComponent({ |
||||
name: 'AFlex', |
||||
inheritAttrs: false, |
||||
props: flexProps(), |
||||
setup(props, { slots, attrs }) { |
||||
const { flex: ctxFlex, direction: ctxDirection } = useConfigContextInject(); |
||||
const { prefixCls } = useConfigInject('flex', props); |
||||
const [wrapSSR, hashId] = useStyle(prefixCls); |
||||
const flexRef = shallowRef(); |
||||
|
||||
return () => { |
||||
const { flex, gap, vertical = false, component: Component = 'div', ...othersProps } = props; |
||||
|
||||
const mergedVertical = vertical ?? ctxFlex?.value.vertical; |
||||
|
||||
const mergedCls = classNames( |
||||
attrs.class, |
||||
prefixCls.value, |
||||
hashId.value, |
||||
createFlexClassNames(prefixCls.value, props), |
||||
{ |
||||
[`${prefixCls.value}-rtl`]: ctxDirection.value === 'rtl', |
||||
[`${prefixCls.value}-gap-${gap}`]: isPresetSize(gap), |
||||
[`${prefixCls.value}-vertical`]: mergedVertical, |
||||
}, |
||||
); |
||||
|
||||
const mergedStyle = { ...(attrs.style as CSSProperties) }; |
||||
|
||||
if (flex) { |
||||
mergedStyle.flex = flex; |
||||
} |
||||
|
||||
if (gap && !isPresetSize(gap)) { |
||||
mergedStyle.gap = `${gap}px`; |
||||
} |
||||
|
||||
return wrapSSR( |
||||
<Component |
||||
ref={flexRef.value} |
||||
class={mergedCls} |
||||
style={mergedStyle} |
||||
{...omit(othersProps, ['justify', 'wrap', 'align'])} |
||||
> |
||||
{slots.default?.()} |
||||
</Component>, |
||||
); |
||||
}; |
||||
}, |
||||
}); |
||||
|
||||
export default withInstall(AFlex); |
||||
export type { FlexProps }; |
@ -0,0 +1,33 @@
|
||||
--- |
||||
category: Components |
||||
subtitle: 弹性布局 |
||||
type: 布局 |
||||
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 |
||||
--- |
||||
|
||||
## 何时使用 |
||||
|
||||
- 适合设置元素之间的间距。 |
||||
- 适合设置各种水平、垂直对齐方式。 |
||||
|
||||
### 与 Space 组件的区别 |
||||
|
||||
- Space 为内联元素提供间距,其本身会为每一个子元素添加包裹元素用于内联对齐。适用于行、列中多个子元素的等距排列。 |
||||
- Flex 为块级元素提供间距,其本身不会添加包裹元素。适用于垂直或水平方向上的子元素布局,并提供了更多的灵活性和控制能力。 |
||||
|
||||
## API |
||||
|
||||
> 自 `ant-design-vue@4.0.7` 版本开始提供该组件。Flex 组件默认行为在水平模式下,为向上对齐,在垂直模式下,为拉伸对齐,你可以通过属性进行调整。 |
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 | |
||||
| --- | --- | --- | --- | --- | |
||||
| 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 | 自定义元素类型 | Component | `div` | | |
@ -0,0 +1,16 @@
|
||||
import type { CSSProperties, ExtractPropTypes } from 'vue'; |
||||
import type { SizeType } from '../config-provider/SizeContext'; |
||||
import { anyType, booleanType, someType, stringType } from '../_util/type'; |
||||
|
||||
export const flexProps = () => ({ |
||||
prefixCls: stringType(), |
||||
vertical: booleanType(), |
||||
wrap: stringType<CSSProperties['flex-wrap']>(), |
||||
justify: stringType<CSSProperties['justify-content']>(), |
||||
align: stringType<CSSProperties['align-items']>(), |
||||
flex: someType<CSSProperties['flex']>([Number, String]), |
||||
gap: someType<CSSProperties['gap'] | SizeType>([Number, String]), |
||||
component: anyType(), |
||||
}); |
||||
|
||||
export type FlexProps = Partial<ExtractPropTypes<ReturnType<typeof flexProps>> & HTMLElement>; |
@ -0,0 +1,111 @@
|
||||
import type { CSSInterpolation } from '../../_util/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<FlexToken> = token => { |
||||
const { componentCls } = token; |
||||
return { |
||||
[componentCls]: { |
||||
display: 'flex', |
||||
'&-vertical': { |
||||
flexDirection: 'column', |
||||
}, |
||||
'&-rtl': { |
||||
direction: 'rtl', |
||||
}, |
||||
'&:empty': { |
||||
display: 'none', |
||||
}, |
||||
}, |
||||
}; |
||||
}; |
||||
|
||||
const genFlexGapStyle: GenerateStyle<FlexToken> = token => { |
||||
const { componentCls } = token; |
||||
return { |
||||
[componentCls]: { |
||||
'&-gap-small': { |
||||
gap: token.flexGapSM, |
||||
}, |
||||
'&-gap-middle': { |
||||
gap: token.flexGap, |
||||
}, |
||||
'&-gap-large': { |
||||
gap: token.flexGapLG, |
||||
}, |
||||
}, |
||||
}; |
||||
}; |
||||
|
||||
const genFlexWrapStyle: GenerateStyle<FlexToken> = token => { |
||||
const { componentCls } = token; |
||||
const wrapStyle: CSSInterpolation = {}; |
||||
flexWrapValues.forEach(value => { |
||||
wrapStyle[`${componentCls}-wrap-${value}`] = { flexWrap: value }; |
||||
}); |
||||
return wrapStyle; |
||||
}; |
||||
|
||||
const genAlignItemsStyle: GenerateStyle<FlexToken> = token => { |
||||
const { componentCls } = token; |
||||
const alignStyle: CSSInterpolation = {}; |
||||
alignItemsValues.forEach(value => { |
||||
alignStyle[`${componentCls}-align-${value}`] = { alignItems: value }; |
||||
}); |
||||
return alignStyle; |
||||
}; |
||||
|
||||
const genJustifyContentStyle: GenerateStyle<FlexToken> = 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<FlexToken>(token, { |
||||
flexGapSM: token.paddingXS, |
||||
flexGap: token.padding, |
||||
flexGapLG: token.paddingLG, |
||||
}); |
||||
return [ |
||||
genFlexStyle(flexToken), |
||||
genFlexGapStyle(flexToken), |
||||
genFlexWrapStyle(flexToken), |
||||
genAlignItemsStyle(flexToken), |
||||
genJustifyContentStyle(flexToken), |
||||
]; |
||||
}); |
@ -0,0 +1,68 @@
|
||||
import classNames from '../_util/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<PropertyKey, boolean> = {}; |
||||
flexWrapValues.forEach(cssKey => { |
||||
wrapCls[`${prefixCls}-wrap-${cssKey}`] = props.wrap === cssKey; |
||||
}); |
||||
return wrapCls; |
||||
}; |
||||
|
||||
const genClsAlign = (prefixCls: string, props: FlexProps) => { |
||||
const alignCls: Record<PropertyKey, boolean> = {}; |
||||
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<PropertyKey, boolean> = {}; |
||||
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; |
Loading…
Reference in new issue