Browse Source

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
selicens 1 year ago committed by GitHub
parent
commit
dae7262f13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      components/_util/gapSize.ts
  2. 2
      components/_util/type.ts
  3. 3
      components/components.ts
  4. 3
      components/config-provider/context.ts
  5. 146
      components/flex/__testts__/__snapshots__/demo.test.js.snap
  6. 3
      components/flex/__testts__/demo.test.js
  7. 58
      components/flex/__testts__/index.test.js
  8. 56
      components/flex/demo/align.vue
  9. 43
      components/flex/demo/basic.vue
  10. 48
      components/flex/demo/combination.vue
  11. 47
      components/flex/demo/gap.vue
  12. 30
      components/flex/demo/index.vue
  13. 23
      components/flex/demo/wrap.vue
  14. 32
      components/flex/index.en-US.md
  15. 66
      components/flex/index.tsx
  16. 33
      components/flex/index.zh-CN.md
  17. 16
      components/flex/interface.ts
  18. 111
      components/flex/style/index.ts
  19. 68
      components/flex/utils.ts
  20. 2
      components/theme/interface/components.ts

13
components/_util/gapSize.ts

@ -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);
}

2
components/_util/type.ts

@ -92,3 +92,5 @@ export function someType<T>(types?: any[], defaultVal?: T) {
}
export type CustomSlotsType<T> = SlotsType<T>;
export type AnyObject = Record<PropertyKey, any>;

3
components/components.ts

@ -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';

3
components/config-provider/context.ts

@ -151,6 +151,9 @@ export interface ConfigProviderInnerProps {
wave?: ComputedRef<{
disabled?: boolean;
}>;
flex?: ComputedRef<{
vertical?: boolean;
}>;
}
export const configProviderKey: InjectionKey<ConfigProviderInnerProps> = Symbol('configProvider');

146
components/flex/__testts__/__snapshots__/demo.test.js.snap

@ -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>
`;

3
components/flex/__testts__/demo.test.js

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('flex');

58
components/flex/__testts__/index.test.js

@ -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();
});
});

56
components/flex/demo/align.vue

@ -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>

43
components/flex/demo/basic.vue

@ -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>

48
components/flex/demo/combination.vue

@ -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>

47
components/flex/demo/gap.vue

@ -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>

30
components/flex/demo/index.vue

@ -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>

23
components/flex/demo/wrap.vue

@ -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>

32
components/flex/index.en-US.md

@ -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` | |

66
components/flex/index.tsx

@ -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 };

33
components/flex/index.zh-CN.md

@ -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` | |

16
components/flex/interface.ts

@ -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>;

111
components/flex/style/index.ts

@ -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),
];
});

68
components/flex/utils.ts

@ -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;

2
components/theme/interface/components.ts

@ -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…
Cancel
Save