Skip to content

Commit 8433875

Browse files
committed
refactor(flex): use SFC
1 parent 5cc4a63 commit 8433875

File tree

10 files changed

+379
-0
lines changed

10 files changed

+379
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<template>
2+
<a-flex gap="middle" vertical>
3+
<label>
4+
Select axis:
5+
<select v-model="axis">
6+
<option v-for="item in axisOptions" :key="item">{{ item }}</option>
7+
</select>
8+
</label>
9+
<a-flex :vertical="axis === 'vertical'">
10+
<div
11+
v-for="(item, index) in new Array(4)"
12+
:key="item"
13+
:style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }"
14+
/>
15+
</a-flex>
16+
<hr/>
17+
<label>
18+
Select justify:
19+
<select v-model="justify">
20+
<option v-for="item in justifyOptions" :key="item">{{ item }}</option>
21+
</select>
22+
</label>
23+
<label>
24+
Select align:
25+
<select v-model="align">
26+
<option v-for="item in alignOptions" :key="item">{{ item }}</option>
27+
</select>
28+
</label>
29+
<a-flex :style="{ ...boxStyle }" :justify="justify" :align="align">
30+
<a-button variant="solid">Primary</a-button>
31+
<a-button variant="solid">Primary</a-button>
32+
<a-button variant="solid">Primary</a-button>
33+
<a-button variant="solid">Primary</a-button>
34+
</a-flex>
35+
<hr/>
36+
<a-flex gap="middle" vertical>
37+
<label>
38+
Select gap size:
39+
<select v-model="gapSize">
40+
<option v-for="item in gapSizeOptions" :key="item">{{ item }}</option>
41+
</select>
42+
</label>
43+
<a-flex :gap="gapSize">
44+
<a-button variant="solid">Primary</a-button>
45+
<a-button>Default</a-button>
46+
<a-button variant="dashed">Dashed</a-button>
47+
<a-button variant="link">Link</a-button>
48+
</a-flex>
49+
</a-flex>
50+
<hr/>
51+
<label>
52+
Auto wrap:
53+
</label>
54+
<a-flex wrap="wrap" gap="small">
55+
<a-button v-for="item in new Array(24)" :key="item" variant="solid">Button</a-button>
56+
</a-flex>
57+
</a-flex>
58+
</template>
59+
60+
<script setup lang="ts">
61+
import type { CSSProperties } from 'vue';
62+
import { ref, reactive } from 'vue';
63+
64+
const baseStyle: CSSProperties = {
65+
width: '25%',
66+
height: '54px',
67+
};
68+
const boxStyle: CSSProperties = {
69+
width: '100%',
70+
height: '120px',
71+
borderRadius: '6px',
72+
border: '1px solid #40a9ff',
73+
};
74+
75+
const axisOptions = reactive(['horizontal', 'vertical']);
76+
const axis = ref(axisOptions[0]);
77+
78+
const justifyOptions = reactive([
79+
'flex-start',
80+
'center',
81+
'flex-end',
82+
'space-between',
83+
'space-around',
84+
'space-evenly',
85+
]);
86+
const justify = ref(justifyOptions[0]);
87+
88+
const alignOptions = reactive(['flex-start', 'center', 'flex-end']);
89+
const align = ref(alignOptions[0]);
90+
91+
const gapSizeOptions = reactive(['small', 'middle', 'large']);
92+
const gapSize = ref(gapSizeOptions[0]);
93+
</script>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script setup lang="ts">
2+
import type { CSSProperties } from 'vue'
3+
import { computed, withDefaults } from 'vue'
4+
import { type FlexProps, flexDefaultProps } from './meta'
5+
import { isPresetSize } from '@/utils/gapSize'
6+
import createFlexClassNames from './utils'
7+
8+
defineOptions({ name: 'AFlex' })
9+
const props = withDefaults(defineProps<FlexProps>(), flexDefaultProps)
10+
11+
const mergedCls = computed(() => [
12+
createFlexClassNames(props.prefixCls, props),
13+
{
14+
'ant-flex': true,
15+
'ant-flex-vertical': props.vertical,
16+
'ant-flex-rtl': false,
17+
[`ant-flex-gap-${props.gap}`]: isPresetSize(props.gap),
18+
}
19+
])
20+
21+
const mergedStyle = computed(() => {
22+
const style: CSSProperties = {}
23+
24+
if (props.flex) {
25+
style.flex = props.flex
26+
}
27+
28+
if (props.gap && !isPresetSize(props.gap)) {
29+
style.gap = `${props.gap}px`
30+
}
31+
32+
return style
33+
})
34+
</script>
35+
36+
<template>
37+
<component :is="componentTag" :class="[$attrs.class, mergedCls]" :style="[$attrs.style, mergedStyle]" v-bind="$attrs">
38+
<slot />
39+
</component>
40+
</template>
41+
42+
<style scoped></style>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { App, Plugin } from 'vue'
2+
import Flex from './Flex.vue'
3+
import './style/index.css'
4+
5+
6+
export { default as Flex } from './Flex.vue'
7+
export * from './meta'
8+
9+
Flex.install = function (app: App) {
10+
app.component('AFlex', Flex)
11+
return app
12+
}
13+
14+
export default Flex as typeof Flex & Plugin
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CSSProperties } from "vue"
2+
3+
type SizeType = 'small' | 'middle' | 'large' | undefined
4+
5+
export type FlexProps = {
6+
prefixCls?: string
7+
rootClassName?: string
8+
vertical?: boolean
9+
wrap?: CSSProperties['flexWrap'] | boolean
10+
justify?: CSSProperties['justifyContent']
11+
align?: CSSProperties['alignItems']
12+
flex?: CSSProperties['flex']
13+
gap?: CSSProperties['gap'] | SizeType
14+
componentTag?: any
15+
}
16+
17+
export const flexDefaultProps = {
18+
prefixCls: 'ant-flex',
19+
componentTag: 'div',
20+
} as const
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
@reference '../../../style/tailwind.css';
2+
3+
.ant-flex {
4+
@apply flex;
5+
@apply m-0;
6+
@apply p-0;
7+
&:where(.ant-flex-vertical) {
8+
@apply flex-col;
9+
}
10+
&:where(.ant-flex-rtl) {
11+
@apply flex-row-reverse;
12+
}
13+
/* gap */
14+
&:where(.ant-flex-gap-small) {
15+
@apply gap-[8px];
16+
}
17+
&:where(.ant-flex-gap-middle) {
18+
@apply gap-[16px];
19+
}
20+
&:where(.ant-flex-gap-large) {
21+
@apply gap-[32px];
22+
}
23+
/* wrap */
24+
&:where(.ant-flex-wrap-wrap) {
25+
@apply flex-wrap;
26+
}
27+
&:where(.ant-flex-wrap-nowrap) {
28+
@apply flex-nowrap;
29+
}
30+
&:where(.ant-flex-wrap-wrap-reverse) {
31+
@apply flex-wrap-reverse;
32+
}
33+
/* align */
34+
&:where(.ant-flex-align-center) {
35+
@apply items-center;
36+
}
37+
&:where(.ant-flex-align-start) {
38+
@apply items-start;
39+
}
40+
&:where(.ant-flex-align-end) {
41+
@apply items-end;
42+
}
43+
&:where(.ant-flex-align-flex-start) {
44+
@apply items-start;
45+
}
46+
&:where(.ant-flex-align-flex-end) {
47+
@apply items-end;
48+
}
49+
&:where(.ant-flex-align-self-start) {
50+
@apply self-start;
51+
}
52+
&:where(.ant-flex-align-self-end) {
53+
@apply self-end;
54+
}
55+
&:where(.ant-flex-align-baseline) {
56+
@apply items-baseline;
57+
}
58+
&:where(.ant-flex-align-normal) {
59+
@apply content-normal;
60+
}
61+
&:where(.ant-flex-align-stretch) {
62+
@apply items-stretch;
63+
}
64+
/* justify */
65+
&:where(.ant-flex-justify-flex-start) {
66+
@apply justify-start;
67+
}
68+
&:where(.ant-flex-justify-flex-end) {
69+
@apply justify-end;
70+
}
71+
&:where(.ant-flex-justify-start) {
72+
@apply justify-start;
73+
}
74+
&:where(.ant-flex-justify-end) {
75+
@apply justify-end;
76+
}
77+
&:where(.ant-flex-justify-center) {
78+
@apply justify-center;
79+
}
80+
&:where(.ant-flex-justify-space-between) {
81+
@apply justify-between;
82+
}
83+
&:where(.ant-flex-justify-space-around) {
84+
@apply justify-around;
85+
}
86+
&:where(.ant-flex-justify-space-evenly) {
87+
@apply justify-evenly;
88+
}
89+
&:where(.ant-flex-justify-stretch) {
90+
@apply justify-stretch;
91+
}
92+
&:where(.ant-flex-justify-normal) {
93+
@apply justify-normal;
94+
}
95+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import classNames from '../../utils/classNames'
2+
3+
import type { FlexProps } from './meta'
4+
5+
export const flexWrapValues = ['wrap', 'nowrap', 'wrap-reverse'] as const
6+
7+
export const justifyContentValues = [
8+
'flex-start',
9+
'flex-end',
10+
'start',
11+
'end',
12+
'center',
13+
'space-between',
14+
'space-around',
15+
'space-evenly',
16+
'stretch',
17+
'normal',
18+
'left',
19+
'right',
20+
] as const
21+
22+
export const alignItemsValues = [
23+
'center',
24+
'start',
25+
'end',
26+
'flex-start',
27+
'flex-end',
28+
'self-start',
29+
'self-end',
30+
'baseline',
31+
'normal',
32+
'stretch',
33+
] as const
34+
35+
const genClsWrap = (prefixCls: string, props: FlexProps) => {
36+
const wrapCls: Record<PropertyKey, boolean> = {}
37+
flexWrapValues.forEach(cssKey => {
38+
// Handle both boolean attribute (wrap="wrap") and string value (wrap="wrap")
39+
const isMatch = props.wrap === true && cssKey === 'wrap' || props.wrap === cssKey
40+
wrapCls[`${prefixCls}-wrap-${cssKey}`] = isMatch
41+
})
42+
return wrapCls
43+
}
44+
45+
const genClsAlign = (prefixCls: string, props: FlexProps) => {
46+
const alignCls: Record<PropertyKey, boolean> = {}
47+
alignItemsValues.forEach(cssKey => {
48+
alignCls[`${prefixCls}-align-${cssKey}`] = props.align === cssKey
49+
})
50+
alignCls[`${prefixCls}-align-stretch`] = !props.align && !!props.vertical
51+
return alignCls
52+
}
53+
54+
const genClsJustify = (prefixCls: string, props: FlexProps) => {
55+
const justifyCls: Record<PropertyKey, boolean> = {}
56+
justifyContentValues.forEach(cssKey => {
57+
justifyCls[`${prefixCls}-justify-${cssKey}`] = props.justify === cssKey
58+
})
59+
return justifyCls
60+
}
61+
62+
function createFlexClassNames(prefixCls: string, props: FlexProps) {
63+
return classNames({
64+
...genClsWrap(prefixCls, props),
65+
...genClsAlign(prefixCls, props),
66+
...genClsJustify(prefixCls, props),
67+
})
68+
}
69+
70+
export default createFlexClassNames

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export { default as Button } from './button'
22
export { default as Input } from './input'
33
export { default as Theme } from './theme'
44
export { default as Affix } from './affix'
5+
export { default as Flex } from './flex'

packages/ui/src/utils/classNames.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { isArray, isString, isObject } from './util'
2+
function classNames(...args: any[]) {
3+
const classes = []
4+
for (let i = 0; i < args.length; i++) {
5+
const value = args[i]
6+
if (!value) continue
7+
if (isString(value)) {
8+
classes.push(value)
9+
} else if (isArray(value)) {
10+
for (let i = 0; i < value.length; i++) {
11+
const inner = classNames(value[i])
12+
if (inner) {
13+
classes.push(inner)
14+
}
15+
}
16+
} else if (isObject(value)) {
17+
for (const name in value) {
18+
if (value[name]) {
19+
classes.push(name)
20+
}
21+
}
22+
}
23+
}
24+
return classes.join(' ')
25+
}
26+
27+
export default classNames

packages/ui/src/utils/gapSize.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export type SizeType = 'small' | 'middle' | 'large' | undefined
2+
3+
export function isPresetSize(size?: SizeType | string | number): size is SizeType {
4+
return ['small', 'middle', 'large'].includes(size as string)
5+
}
6+
7+
export function isValidGapNumber(size?: SizeType | string | number): size is number {
8+
if (!size) {
9+
// 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.
10+
return false
11+
}
12+
return typeof size === 'number' && !Number.isNaN(size)
13+
}

packages/ui/src/utils/util.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const isArray = Array.isArray
2+
export const isString = val => typeof val === 'string'
3+
export const isSymbol = val => typeof val === 'symbol'
4+
export const isObject = val => val !== null && typeof val === 'object'

0 commit comments

Comments
 (0)