feat(Flex): New component (#7052)
* 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
parent
8cf6be11f7
commit
dae7262f13
|
@ -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);
|
||||
}
|
|
@ -92,3 +92,5 @@ export function someType<T>(types?: any[], defaultVal?: T) {
|
|||
}
|
||||
|
||||
export type CustomSlotsType<T> = SlotsType<T>;
|
||||
|
||||
export type AnyObject = Record<PropertyKey, any>;
|
||||
|
|
|
@ -264,3 +264,6 @@ export { default as Tour } from './tour';
|
|||
|
||||
export type { AppProps } from './app';
|
||||
export { default as App } from './app';
|
||||
|
||||
export type { FlexProps } from './flex';
|
||||
export { default as Flex } from './flex';
|
||||
|
|
|
@ -151,6 +151,9 @@ export interface ConfigProviderInnerProps {
|
|||
wave?: ComputedRef<{
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
flex?: ComputedRef<{
|
||||
vertical?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const configProviderKey: InjectionKey<ConfigProviderInnerProps> = Symbol('configProvider');
|
||||
|
|
|
@ -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;
|
|
@ -48,6 +48,7 @@ import type { ComponentToken as TourComponentToken } from '../../tour/style';
|
|||
import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
|
||||
import type { ComponentToken as AppComponentToken } from '../../app/style';
|
||||
import type { ComponentToken as WaveToken } from '../../_util/wave/style';
|
||||
import type { ComponentToken as FlexToken } from '../../flex/style';
|
||||
|
||||
export interface ComponentTokenMap {
|
||||
Affix?: {};
|
||||
|
@ -116,4 +117,5 @@ export interface ComponentTokenMap {
|
|||
|
||||
// /** @private Internal TS definition. Do not use. */
|
||||
Wave?: WaveToken;
|
||||
Flex?: FlexToken;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue