parent
							
								
									5cc4a63480
								
							
						
					
					
						commit
						a15bb9cfb8
					
				|  | @ -0,0 +1,93 @@ | |||
| <template> | ||||
|   <a-flex gap="middle" vertical> | ||||
|     <label> | ||||
|       Select axis: | ||||
|       <select v-model="axis"> | ||||
|         <option v-for="item in axisOptions" :key="item">{{ item }}</option> | ||||
|       </select> | ||||
|     </label> | ||||
|     <a-flex :vertical="axis === 'vertical'"> | ||||
|       <div | ||||
|         v-for="(item, index) in new Array(4)" | ||||
|         :key="item" | ||||
|         :style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }" | ||||
|       /> | ||||
|     </a-flex> | ||||
|     <hr/> | ||||
|     <label> | ||||
|       Select justify: | ||||
|       <select v-model="justify"> | ||||
|         <option v-for="item in justifyOptions" :key="item">{{ item }}</option> | ||||
|       </select> | ||||
|     </label> | ||||
|     <label> | ||||
|       Select align: | ||||
|       <select v-model="align"> | ||||
|         <option v-for="item in alignOptions" :key="item">{{ item }}</option> | ||||
|       </select> | ||||
|     </label> | ||||
|     <a-flex :style="{ ...boxStyle }" :justify="justify" :align="align"> | ||||
|       <a-button variant="solid">Primary</a-button> | ||||
|       <a-button variant="solid">Primary</a-button> | ||||
|       <a-button variant="solid">Primary</a-button> | ||||
|       <a-button variant="solid">Primary</a-button> | ||||
|     </a-flex> | ||||
|     <hr/> | ||||
|     <a-flex gap="middle" vertical> | ||||
|       <label> | ||||
|         Select gap size: | ||||
|         <select v-model="gapSize"> | ||||
|           <option v-for="item in gapSizeOptions" :key="item">{{ item }}</option> | ||||
|         </select> | ||||
|       </label> | ||||
|       <a-flex :gap="gapSize"> | ||||
|         <a-button variant="solid">Primary</a-button> | ||||
|         <a-button>Default</a-button> | ||||
|         <a-button variant="dashed">Dashed</a-button> | ||||
|         <a-button variant="link">Link</a-button> | ||||
|       </a-flex> | ||||
|     </a-flex> | ||||
|     <hr/> | ||||
|     <label> | ||||
|       Auto wrap: | ||||
|     </label> | ||||
|     <a-flex wrap="wrap" gap="small"> | ||||
|       <a-button v-for="item in new Array(24)" :key="item" variant="solid">Button</a-button> | ||||
|     </a-flex> | ||||
|   </a-flex> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
|   import type { CSSProperties } from 'vue'; | ||||
|   import { ref, reactive } from 'vue'; | ||||
| 
 | ||||
|   const baseStyle: CSSProperties = { | ||||
|     width: '25%', | ||||
|     height: '54px', | ||||
|   }; | ||||
|   const boxStyle: CSSProperties = { | ||||
|     width: '100%', | ||||
|     height: '120px', | ||||
|     borderRadius: '6px', | ||||
|     border: '1px solid #40a9ff', | ||||
|   }; | ||||
| 
 | ||||
|   const axisOptions = reactive(['horizontal', 'vertical']); | ||||
|   const axis = ref(axisOptions[0]); | ||||
| 
 | ||||
|   const justifyOptions = reactive([ | ||||
|   'flex-start', | ||||
|   'center', | ||||
|   'flex-end', | ||||
|   'space-between', | ||||
|   'space-around', | ||||
|   'space-evenly', | ||||
|   ]); | ||||
|   const justify = ref(justifyOptions[0]); | ||||
| 
 | ||||
|   const alignOptions = reactive(['flex-start', 'center', 'flex-end']); | ||||
|   const align = ref(alignOptions[0]); | ||||
| 
 | ||||
|   const gapSizeOptions = reactive(['small', 'middle', 'large']); | ||||
|   const gapSize = ref(gapSizeOptions[0]); | ||||
| </script> | ||||
|  | @ -0,0 +1,42 @@ | |||
| <script setup lang="ts"> | ||||
|   import type { CSSProperties } from 'vue' | ||||
|   import { computed, withDefaults } from 'vue' | ||||
|   import { type FlexProps, flexDefaultProps } from './meta' | ||||
|   import { isPresetSize } from '@/utils/gapSize' | ||||
|   import createFlexClassNames from './utils' | ||||
| 
 | ||||
|   defineOptions({ name: 'AFlex' }) | ||||
|   const props = withDefaults(defineProps<FlexProps>(), flexDefaultProps) | ||||
| 
 | ||||
|   const mergedCls = computed(() => [ | ||||
|     createFlexClassNames(props.prefixCls, props), | ||||
|     { | ||||
|       'ant-flex': true, | ||||
|       'ant-flex-vertical': props.vertical, | ||||
|       'ant-flex-rtl': false, | ||||
|       [`ant-flex-gap-${props.gap}`]: isPresetSize(props.gap), | ||||
|     } | ||||
|   ]) | ||||
| 
 | ||||
|   const mergedStyle = computed(() => { | ||||
|     const style: CSSProperties = {} | ||||
|      | ||||
|     if (props.flex) { | ||||
|       style.flex = props.flex | ||||
|     } | ||||
| 
 | ||||
|     if (props.gap && !isPresetSize(props.gap)) { | ||||
|       style.gap = `${props.gap}px` | ||||
|     } | ||||
| 
 | ||||
|     return style | ||||
|   }) | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <component :is="componentTag" :class="[$attrs.class, mergedCls]" :style="[$attrs.style, mergedStyle]" v-bind="$attrs"> | ||||
|     <slot /> | ||||
|   </component> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
|  | @ -0,0 +1,7 @@ | |||
| // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||||
| 
 | ||||
| exports[`Flex > should render correctly 1`] = ` | ||||
| "<div class="ant-flex"> | ||||
|   <div>test</div> | ||||
| </div>" | ||||
| `; | ||||
|  | @ -0,0 +1,149 @@ | |||
| import { describe, expect, it, vi } from 'vitest' | ||||
| import { Flex } from '@ant-design-vue/ui' | ||||
| import { mount } from '@vue/test-utils' | ||||
| 
 | ||||
| describe('Flex', () => { | ||||
|   it('should render correctly', () => { | ||||
|     const wrapper = mount(Flex, { | ||||
|       slots: { | ||||
|         default: `<div>test</div>`, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(wrapper.html()).toMatchSnapshot(); | ||||
|   }); | ||||
| 
 | ||||
|   it('Flex', () => { | ||||
|     const wrapper = mount(Flex, { | ||||
|       props: { | ||||
|         justify: 'center' | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>` | ||||
|       }, | ||||
|     }); | ||||
|      | ||||
|     const wrapper3 = mount(Flex, { | ||||
|       props: { | ||||
|         flex: '0 1 auto', | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>`, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(wrapper.classes('ant-flex')).toBeTruthy(); | ||||
|     expect(wrapper.find('.ant-flex-justify-center')).toBeTruthy(); | ||||
|     expect(wrapper3.classes('ant-flex')).toBeTruthy(); | ||||
|     expect(wrapper3.element.style.flex).toBe('0 1 auto'); | ||||
|   }); | ||||
| 
 | ||||
|   describe('Props: gap', () => { | ||||
|     it('support string', () => { | ||||
|       const wrapper = mount(Flex, { | ||||
|         props: { | ||||
|           gap: 'inherit', | ||||
|         }, | ||||
|         slots: { | ||||
|           default: `<div>test</div>`, | ||||
|         }, | ||||
|       }); | ||||
|       expect(wrapper.classes('ant-flex')).toBeTruthy(); | ||||
|       expect(wrapper.element.style.gap).toBe('inherit'); | ||||
|     }); | ||||
| 
 | ||||
|     it('support number', () => { | ||||
|       const wrapper = mount(Flex, { | ||||
|         props: { | ||||
|           gap: '100', | ||||
|         }, | ||||
|         slots: { | ||||
|           default: `<div>test</div>`, | ||||
|         }, | ||||
|       }); | ||||
|       expect(wrapper.classes('ant-flex')).toBeTruthy(); | ||||
|       expect(wrapper.element.style.gap).toBe('100px'); | ||||
|     }); | ||||
| 
 | ||||
|     it('support preset size', () => { | ||||
|       const wrapper = mount(Flex, { | ||||
|         props: { | ||||
|           gap: 'small', | ||||
|         }, | ||||
|         slots: { | ||||
|           default: `<div>test</div>`, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       expect(wrapper.classes('ant-flex')).toBeTruthy(); | ||||
|       expect(wrapper.classes('ant-flex-gap-small')).toBeTruthy(); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   it('Component work', () => { | ||||
|     const wrapper = mount(Flex, { | ||||
|       slots: { | ||||
|         default: `<div>test</div>` | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     const wrapper2 = mount(Flex, { | ||||
|       props: { | ||||
|         componentTag: 'span' | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>` | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     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(Flex, { | ||||
|       props: { | ||||
|         vertical: true | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>` | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     const wrapper2 = mount(Flex, { | ||||
|       props: { | ||||
|         vertical: true, | ||||
|         align: 'center', | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>`, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(wrapper.find('.ant-flex-align-stretch')).toBeTruthy(); | ||||
|     expect(wrapper2.find('.ant-flex-align-center')).toBeTruthy(); | ||||
|   }); | ||||
| 
 | ||||
|   it('wrap prop shouled support boolean', () => { | ||||
|     const wrapper = mount(Flex, { | ||||
|       props: { | ||||
|         wrap: 'wrap', | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>`, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     const wrapper2 = mount(Flex, { | ||||
|       props: { | ||||
|         wrap: true, | ||||
|       }, | ||||
|       slots: { | ||||
|         default: `<div>test</div>`, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     expect(wrapper.classes('ant-flex-wrap-wrap')).toBeTruthy(); | ||||
|     expect(wrapper2.classes('ant-flex-wrap-wrap')).toBeTruthy(); | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,14 @@ | |||
| import { App, Plugin } from 'vue' | ||||
| import Flex from './Flex.vue' | ||||
| import './style/index.css' | ||||
| 
 | ||||
| 
 | ||||
| export { default as Flex } from './Flex.vue' | ||||
| export * from './meta' | ||||
| 
 | ||||
| Flex.install = function (app: App) { | ||||
|   app.component('AFlex', Flex) | ||||
|   return app | ||||
| } | ||||
| 
 | ||||
| export default Flex as typeof Flex & Plugin | ||||
|  | @ -0,0 +1,20 @@ | |||
| import { CSSProperties } from "vue" | ||||
| 
 | ||||
| type SizeType = 'small' | 'middle' | 'large' | undefined | ||||
| 
 | ||||
| export type FlexProps = { | ||||
|   prefixCls?: string | ||||
|   rootClassName?: string | ||||
|   vertical?: boolean | ||||
|   wrap?: CSSProperties['flexWrap'] | boolean | ||||
|   justify?: CSSProperties['justifyContent'] | ||||
|   align?: CSSProperties['alignItems'] | ||||
|   flex?: CSSProperties['flex'] | ||||
|   gap?: CSSProperties['gap'] | SizeType | ||||
|   componentTag?: any | ||||
| } | ||||
| 
 | ||||
| export const flexDefaultProps = { | ||||
|   prefixCls: 'ant-flex', | ||||
|   componentTag: 'div', | ||||
| } as const | ||||
|  | @ -0,0 +1,95 @@ | |||
| @reference '../../../style/tailwind.css'; | ||||
| 
 | ||||
| .ant-flex { | ||||
|   @apply flex; | ||||
|   @apply m-0; | ||||
|   @apply p-0; | ||||
|   &:where(.ant-flex-vertical) { | ||||
|     @apply flex-col; | ||||
|   } | ||||
|   &:where(.ant-flex-rtl) { | ||||
|     @apply flex-row-reverse; | ||||
|   } | ||||
|   /* gap */ | ||||
|   &:where(.ant-flex-gap-small) { | ||||
|     @apply gap-[8px]; | ||||
|   } | ||||
|   &:where(.ant-flex-gap-middle) { | ||||
|     @apply gap-[16px]; | ||||
|   } | ||||
|   &:where(.ant-flex-gap-large) { | ||||
|     @apply gap-[32px]; | ||||
|   } | ||||
|   /* wrap */ | ||||
|   &:where(.ant-flex-wrap-wrap) { | ||||
|     @apply flex-wrap; | ||||
|   } | ||||
|   &:where(.ant-flex-wrap-nowrap) { | ||||
|     @apply flex-nowrap; | ||||
|   } | ||||
|   &:where(.ant-flex-wrap-wrap-reverse) { | ||||
|     @apply flex-wrap-reverse; | ||||
|   } | ||||
|   /* align */ | ||||
|   &:where(.ant-flex-align-center) { | ||||
|     @apply items-center; | ||||
|   } | ||||
|   &:where(.ant-flex-align-start) { | ||||
|     @apply items-start; | ||||
|   } | ||||
|   &:where(.ant-flex-align-end) { | ||||
|     @apply items-end; | ||||
|   } | ||||
|   &:where(.ant-flex-align-flex-start) { | ||||
|     @apply items-start; | ||||
|   } | ||||
|   &:where(.ant-flex-align-flex-end) { | ||||
|     @apply items-end; | ||||
|   } | ||||
|   &:where(.ant-flex-align-self-start) { | ||||
|     @apply self-start; | ||||
|   } | ||||
|   &:where(.ant-flex-align-self-end) { | ||||
|     @apply self-end; | ||||
|   } | ||||
|   &:where(.ant-flex-align-baseline) { | ||||
|     @apply items-baseline; | ||||
|   } | ||||
|   &:where(.ant-flex-align-normal) { | ||||
|     @apply content-normal; | ||||
|   } | ||||
|   &:where(.ant-flex-align-stretch) { | ||||
|     @apply items-stretch; | ||||
|   } | ||||
|   /* justify */ | ||||
|   &:where(.ant-flex-justify-flex-start) { | ||||
|     @apply justify-start; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-flex-end) { | ||||
|     @apply justify-end; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-start) { | ||||
|     @apply justify-start; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-end) { | ||||
|     @apply justify-end; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-center) { | ||||
|     @apply justify-center; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-space-between) { | ||||
|     @apply justify-between; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-space-around) { | ||||
|     @apply justify-around; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-space-evenly) { | ||||
|     @apply justify-evenly; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-stretch) { | ||||
|     @apply justify-stretch; | ||||
|   } | ||||
|   &:where(.ant-flex-justify-normal) { | ||||
|     @apply justify-normal; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,70 @@ | |||
| import classNames from '../../utils/classNames' | ||||
| 
 | ||||
| import type { FlexProps } from './meta' | ||||
| 
 | ||||
| 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 => { | ||||
|     // Handle both boolean attribute (wrap="wrap") and string value (wrap="wrap")
 | ||||
|     const isMatch = props.wrap === true && cssKey === 'wrap' || props.wrap === cssKey | ||||
|     wrapCls[`${prefixCls}-wrap-${cssKey}`] = isMatch | ||||
|   }) | ||||
|   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,3 +2,4 @@ export { default as Button } from './button' | |||
| export { default as Input } from './input' | ||||
| export { default as Theme } from './theme' | ||||
| export { default as Affix } from './affix' | ||||
| export { default as Flex } from './flex' | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| import { isArray, isString, isObject } from './util' | ||||
| function classNames(...args: any[]) { | ||||
|   const classes = [] | ||||
|   for (let i = 0; i < args.length; i++) { | ||||
|     const value = args[i] | ||||
|     if (!value) continue | ||||
|     if (isString(value)) { | ||||
|       classes.push(value) | ||||
|     } else if (isArray(value)) { | ||||
|       for (let i = 0; i < value.length; i++) { | ||||
|         const inner = classNames(value[i]) | ||||
|         if (inner) { | ||||
|           classes.push(inner) | ||||
|         } | ||||
|       } | ||||
|     } else if (isObject(value)) { | ||||
|       for (const name in value) { | ||||
|         if (value[name]) { | ||||
|           classes.push(name) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return classes.join(' ') | ||||
| } | ||||
| 
 | ||||
| export default classNames | ||||
|  | @ -0,0 +1,13 @@ | |||
| export type SizeType = 'small' | 'middle' | 'large' | undefined | ||||
| 
 | ||||
| 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,4 @@ | |||
| export const isArray = Array.isArray | ||||
| export const isString = val => typeof val === 'string' | ||||
| export const isSymbol = val => typeof val === 'symbol' | ||||
| export const isObject = val => val !== null && typeof val === 'object' | ||||
		Loading…
	
		Reference in New Issue
	
	 selicens
						selicens