Skip to content

Commit

Permalink
feat(comp: space): add component space
Browse files Browse the repository at this point in the history
  • Loading branch information
LaamGinghong committed Jan 5, 2021
1 parent ce3d3e5 commit 4a9d878
Show file tree
Hide file tree
Showing 25 changed files with 688 additions and 11 deletions.
23 changes: 21 additions & 2 deletions packages/cdk/utils/__tests__/vNode.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type { VNode, VNodeChild } from 'vue'

import { Comment, Fragment, Text } from 'vue'
import { Comment, Fragment, Slots, Text } from 'vue'

import { getFirstValidNode, isValidElementNode } from '../vNode'
import { getFirstValidNode, getSlotNodeList, isValidElementNode } from '../vNode'

const TEMPLATE = 'template'

type FakeVNode = Partial<Pick<VNode, 'type'>> & { children?: FakeVNode[] }

type Writable<T> = {
-readonly [P in keyof T]: T[P]
}

const vNode: FakeVNode = {
type: Comment,
}
Expand Down Expand Up @@ -52,4 +56,19 @@ describe('vNode.ts', () => {
vNode.type = Text
expect(isValidElementNode(vNode as VNodeChild)).toBeTruthy()
})

test('getSlotNodeList work', () => {
const slots: Writable<Slots> = {}
expect(getSlotNodeList(slots)).toEqual([])

slots.default = () => [{ type: Fragment, dynamicChildren: [] as VNode[] }] as VNode[]
expect(getSlotNodeList(slots)).toEqual([])

slots.default = () => [{ type: Fragment }] as VNode[]
expect(getSlotNodeList(slots)).toEqual([])

const vNodes: VNode[] = [{ type: Text }] as VNode[]
slots.default = () => vNodes
expect(getSlotNodeList(slots)).toEqual(vNodes)
})
})
26 changes: 21 additions & 5 deletions packages/cdk/utils/vNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { VNode, VNodeChild } from 'vue'

import { Comment, Fragment } from 'vue'
import { Comment, Fragment, Slots } from 'vue'

import { isUndefined } from './typeof'

Expand All @@ -20,8 +20,8 @@ function getChildren(node: VNode, depth: number): undefined | VNode {

/**
* get first valid child node (not fragment not comment)
* @param nodes {VNode} node to be searched
* @param maxDepth {number} depth to be searched, default is 3
* @param nodes node to be searched
* @param maxDepth depth to be searched, default is 3
*/
export function getFirstValidNode(nodes?: VNodeChild, maxDepth = 3): ReturnType<typeof getChildren> {
if (isUndefined(nodes)) return
Expand All @@ -33,9 +33,25 @@ export function getFirstValidNode(nodes?: VNodeChild, maxDepth = 3): ReturnType<

/**
* determine whether an element is valid (not fragment not comment)
* @param node {VNode} node to be determined
* @returns {boolean}
* @param node node to be determined
*/
export function isValidElementNode(node: VNodeChild): boolean {
return !(isFragment(node) || isComment(node))
}

/**
* get all child node (Whatever dynamic or not)
* @param slots slots of the component
* @param key key of slots, default is 'default'
* @param options the property of the render function
*/
export function getSlotNodeList(slots: Slots, key = 'default', options: unknown[] = []): VNode[] {
if (!slots[key]) return []

let vNodes = slots[key]!(options)
if (vNodes.length === 1 && isFragment(vNodes[0])) {
vNodes = vNodes[0].dynamicChildren ?? []
}

return vNodes
}
2 changes: 1 addition & 1 deletion packages/components/components.less
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
@import './divider/style/index.less';
@import './image/style/index.less';
@import './spin/style/index.less';

@import './space/style/index.less';
4 changes: 4 additions & 0 deletions packages/components/core/config/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
DividerConfig,
ImageConfig,
SpinConfig,
SpaceConfig,
} from './types'

import { shallowReactive } from 'vue'
Expand Down Expand Up @@ -37,11 +38,14 @@ const spin = shallowReactive<SpinConfig>({
size: 'small',
})

const space = shallowReactive<SpaceConfig>({ size: 'small' })

export const defaultConfig: GlobalConfig = {
button,
icon,
badge,
divider,
image,
spin,
space,
}
7 changes: 6 additions & 1 deletion packages/components/core/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentSize, ButtonMode, DividerPosition, DividerType, SpinTipAlignType } from '../types'
import type { ComponentSize, ButtonMode, DividerPosition, DividerType, SpinTipAlignType, SpaceSize } from '../types'

export type GlobalConfigKey = keyof GlobalConfig

Expand All @@ -9,6 +9,7 @@ export interface GlobalConfig {
divider: DividerConfig
image: ImageConfig
spin: SpinConfig
space: SpaceConfig
}

export interface ButtonConfig {
Expand Down Expand Up @@ -45,3 +46,7 @@ export interface SpinConfig {
tipAlign: SpinTipAlignType
size: ComponentSize
}

export interface SpaceConfig {
size: SpaceSize
}
2 changes: 2 additions & 0 deletions packages/components/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export type DividerPosition = 'left' | 'center' | 'right'
export type DividerType = 'horizontal' | 'vertical'

export type SpinTipAlignType = 'horizontal' | 'vertical'

export type SpaceSize = 'small' | 'medium' | 'large' | number
4 changes: 3 additions & 1 deletion packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { IxBadge } from './badge'
import { IxDivider } from './divider'
import { IxImage } from './image'
import { IxSpin } from './spin'
import { IxSpace } from './space'

const components = [IxButton, IxButtonGroup, IxIcon, IxBadge, IxDivider, IxImage, IxSpin]
const components = [IxButton, IxButtonGroup, IxIcon, IxBadge, IxDivider, IxImage, IxSpin, IxSpace]

const install = (app: App): void => {
components.forEach(component => {
Expand All @@ -30,3 +31,4 @@ export * from './badge'
export * from './divider'
export * from './image'
export * from './spin'
export * from './space'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Space.vue render work 1`] = `"<div class=\\"ix-space ix-space-baseline ix-space-horizontal\\"><div class=\\"ix-space-item ix-space-item-small\\"> Space </div><!--v-if--><div class=\\"ix-space-item ix-space-item-small\\"><button class=\\"ix-button ix-button-primary\\"><!--v-if--><span>Button</span></button></div><!--v-if--><div class=\\"ix-space-item ix-space-item-small\\"><button class=\\"ix-button\\"><!--v-if--><span>Button</span></button></div><!--v-if--></div>"`;
185 changes: 185 additions & 0 deletions packages/components/space/__tests__/space.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { mount } from '@vue/test-utils'
import { defineComponent, PropType } from 'vue'
import IxSpace from '../src/Space.vue'
import { IxButton } from '../../button'
import { IxDivider } from '../../divider'
import { SpaceAlign, SpaceDirection } from '../src/types'
import { SpaceSize } from '@idux/components'
import { isNil } from '@idux/cdk/utils'

const TestComponent = defineComponent({
components: { IxSpace, IxButton, IxDivider },
template: `
<IxSpace :align='align' :direction='direction' :size='size' :wrap='wrap' :split='split'>
Space
<IxButton mode='primary'>Button</IxButton>
<IxButton>Button</IxButton>
<template v-slot:split v-if='showSplit'>
<IxDivider :type='dividerType' />
</template>
</IxSpace>
`,
props: {
align: { type: String as PropType<SpaceAlign>, default: undefined },
direction: { type: String as PropType<SpaceDirection>, default: undefined },
size: { type: [String, Number, Array] as PropType<SpaceSize>, default: undefined },
split: { type: String, default: undefined },
wrap: { type: Boolean, default: undefined },
},
data() {
return {
showSplit: false,
}
},
computed: {
dividerType(): SpaceDirection {
const hashmap = {
horizontal: 'vertical',
vertical: 'horizontal',
}
return hashmap[this.direction] as SpaceDirection
},
},
})

describe('Space.vue', () => {
test('render work', () => {
const wrapper = mount(TestComponent)
expect(wrapper.classes()).toContain('ix-space')
expect(wrapper.html()).toMatchSnapshot()
})

test('align work', async () => {
const wrapper = mount(TestComponent)
expect(wrapper.classes()).not.toContain('ix-space-start')
expect(wrapper.classes()).not.toContain('ix-space-center')
expect(wrapper.classes()).not.toContain('ix-space-end')
expect(wrapper.classes()).toContain('ix-space-baseline')

await wrapper.setProps({ align: 'start' })
expect(wrapper.classes()).toContain('ix-space-start')
expect(wrapper.classes()).not.toContain('ix-space-center')
expect(wrapper.classes()).not.toContain('ix-space-end')
expect(wrapper.classes()).not.toContain('ix-space-baseline')

await wrapper.setProps({ align: 'center' })
expect(wrapper.classes()).not.toContain('ix-space-start')
expect(wrapper.classes()).toContain('ix-space-center')
expect(wrapper.classes()).not.toContain('ix-space-end')
expect(wrapper.classes()).not.toContain('ix-space-baseline')

await wrapper.setProps({ align: 'end' })
expect(wrapper.classes()).not.toContain('ix-space-start')
expect(wrapper.classes()).not.toContain('ix-space-center')
expect(wrapper.classes()).toContain('ix-space-end')
expect(wrapper.classes()).not.toContain('ix-space-baseline')

await wrapper.setProps({ align: 'baseline' })
expect(wrapper.classes()).not.toContain('ix-space-start')
expect(wrapper.classes()).not.toContain('ix-space-center')
expect(wrapper.classes()).not.toContain('ix-space-end')
expect(wrapper.classes()).toContain('ix-space-baseline')
})

test('direction work', async () => {
const wrapper = mount(TestComponent)
expect(wrapper.classes()).toContain('ix-space-horizontal')
expect(wrapper.classes()).not.toContain('ix-space-vertical')

await wrapper.setProps({ direction: 'vertical' })
expect(wrapper.classes()).not.toContain('ix-space-horizontal')
expect(wrapper.classes()).toContain('ix-space-vertical')
})

test('wrap work', async () => {
const wrapper = mount(TestComponent)
expect(wrapper.classes()).not.toContain('ix-space-wrap')

await wrapper.setProps({ wrap: true })
expect(wrapper.classes()).toContain('ix-space-wrap')
})

test('split work', async () => {
const wrapper = mount(TestComponent)
expect(wrapper.find('.ix-divider').exists()).toBeFalsy()
expect(wrapper.find('.ix-space-split').exists()).toBeFalsy()

await wrapper.setData({ showSplit: true })
expect(wrapper.find('.ix-divider').exists()).toBeTruthy()
expect(wrapper.find('.ix-space-split').exists()).toBeFalsy()

await wrapper.setProps({ split: '/' })
expect(wrapper.find('.ix-divider').exists()).toBeTruthy()
expect(wrapper.find('.ix-space-split').exists()).toBeFalsy()

await wrapper.setData({ showSplit: false })
expect(wrapper.find('.ix-divider').exists()).toBeFalsy()
expect(wrapper.find('.ix-space-split').exists()).toBeTruthy()
})

test('size work', async () => {
const wrapper = mount(TestComponent)
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(3)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)

await wrapper.setProps({ size: 'small' })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(3)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)

await wrapper.setProps({ size: 'medium' })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(3)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)

await wrapper.setProps({ size: 'large' })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(3)

await wrapper.setProps({ size: 20 })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)
expect(
wrapper.findAll('.ix-space-item').every(item => item.attributes('style') === 'margin-right: 20px;'),
).toBeTruthy()

await wrapper.setProps({ split: '/' })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item').every(item => isNil(item.attributes('style')))).toBeTruthy()

await wrapper.setProps({ split: undefined, size: ['small', 'medium'] })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(1)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(1)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)

const size = new Map([
[0, 20],
[1, 10],
])
await wrapper.setProps({ size: Array.from(size.values()) })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)
expect(
wrapper.findAll('.ix-space-item').reduce((pre, cur, index) => {
return pre && cur.attributes('style') === `margin-right: ${size.get(index)}px;`
}, true),
)

await wrapper.setProps({ split: '/' })
expect(wrapper.findAll('.ix-space-item-small').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-medium').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item-large').length).toEqual(0)
expect(wrapper.findAll('.ix-space-item').every(item => isNil(item.attributes('style')))).toBeTruthy()

size.set(2, 30)
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
await wrapper.setProps({ split: undefined, size: Array.from(size.values()) })
expect(warn).toBeCalled()
})
})
25 changes: 25 additions & 0 deletions packages/components/space/demo/CustomSize.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<ix-space :size="size">
Space
<ix-button mode="primary">Button</ix-button>
<ix-button>Button</ix-button>
</ix-space>
<br />
<input v-model="num" type="number" />
</template>

<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import { IxButton, IxSpace } from '@idux/components'
export default defineComponent({
name: 'CustomSize',
components: { IxButton, IxSpace },
setup() {
const num = ref(20)
const size = computed(() => +num.value)
return { num, size }
},
})
</script>
Loading

0 comments on commit 4a9d878

Please sign in to comment.