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 4, 2021
1 parent bc8ff33 commit 83ada1a
Show file tree
Hide file tree
Showing 24 changed files with 647 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/components/components.less
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
@import './badge/style/index.less';
@import './divider/style/index.less';
@import './image/style/index.less';

@import './space/style/index.less';
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 } from '../types'
import type { ComponentSize, ButtonMode, DividerPosition, DividerType, SpaceSize } from '../types'

export type GlobalConfigKey = keyof GlobalConfig

Expand All @@ -8,6 +8,7 @@ export interface GlobalConfig {
badge: BadgeConfig
divider: DividerConfig
image: ImageConfig
space: SpaceConfig
}

export interface ButtonConfig {
Expand Down Expand Up @@ -36,3 +37,7 @@ export interface ImageConfig {
height: string | number
fallback: string
}

export interface SpaceConfig {
size: SpaceSize
}
5 changes: 4 additions & 1 deletion packages/components/core/config/useGlobalConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
IconConfig,
DividerConfig,
ImageConfig,
SpaceConfig,
} from './types'

const button = shallowReactive<ButtonConfig>({ mode: 'default', size: 'medium' })
Expand All @@ -19,20 +20,21 @@ const divider = shallowReactive<DividerConfig>({
position: 'center',
type: 'horizontal',
})

const image: ImageConfig = shallowReactive({
width: 100,
height: 100,
fallback:
'data:image/png;base64,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',
})
const space = shallowReactive<SpaceConfig>({ size: 'small' })

const defaultConfig: GlobalConfig = {
button,
icon,
badge,
divider,
image,
space,
}

const globalConfigToken: Record<GlobalConfigKey, symbol> = {
Expand All @@ -41,6 +43,7 @@ const globalConfigToken: Record<GlobalConfigKey, symbol> = {
badge: Symbol(),
divider: Symbol(),
image: Symbol(),
space: Symbol(),
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/components/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export type ButtonMode = 'primary' | 'default' | 'dashed' | 'text' | 'link'
export type DividerPosition = 'left' | 'center' | 'right'

export type DividerType = 'horizontal' | 'vertical'

export type SpaceSize = 'small' | 'medium' | 'large' | number
13 changes: 13 additions & 0 deletions packages/components/core/utils/getSlotNodeList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Slots, VNode } from 'vue'

interface GetSlotNodeList {
(slots: Slots, key?: string, options?: unknown[]): VNode[]
}

export const getSlotNodeList: GetSlotNodeList = (slots, key = 'default', options = []) => {
if (!slots[key]) return []

const currentSlot = slots[key]!(options)
if (currentSlot.length === 1 && currentSlot[0].dynamicChildren) return currentSlot[0].dynamicChildren
return currentSlot
}
1 change: 1 addition & 0 deletions packages/components/core/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './installComponent'
export * from './isDevMode'
export * from './getSlotNodeList'
4 changes: 3 additions & 1 deletion packages/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { IxIcon } from './icon'
import { IxBadge } from './badge'
import { IxDivider } from './divider'
import { IxImage } from './image'
import { IxSpace } from './space'

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

const install = (app: App): void => {
components.forEach(component => {
Expand All @@ -28,3 +29,4 @@ export * from './icon'
export * from './badge'
export * from './divider'
export * from './image'
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()
})
})
24 changes: 24 additions & 0 deletions packages/components/space/demo/CustomSize.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<ix-space :size="size">
<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>
26 changes: 26 additions & 0 deletions packages/components/space/demo/Size.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<ix-space :size="size">
<ix-button>Button</ix-button>
<ix-button mode="primary">Button</ix-button>
</ix-space>
<br />
<select v-model="size">
<option value="small">small</option>
<option value="medium">medium</option>
<option value="large">large</option>
</select>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import { IxButton, IxSpace } from '@idux/components'
export default defineComponent({
name: 'Size',
components: { IxButton, IxSpace },
setup() {
const size = ref('small')
return { size }
},
})
</script>
15 changes: 15 additions & 0 deletions packages/components/space/demo/Wrap.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<ix-space :wrap="true">
<ix-button v-for="item in 20" :key="item">Button</ix-button>
</ix-space>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { IxButton, IxSpace } from '@idux/components'
export default defineComponent({
name: 'Wrap',
components: { IxButton, IxSpace },
})
</script>
23 changes: 23 additions & 0 deletions packages/components/space/demo/basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
order: 0
title:
zh: 基本用法
en: Basic usage
---

## zh

相邻组件水平间距。

## demo

```html
<template>
<ix-space>
Space
<ix-button mode='primary'>Button</ix-button>
<ix-button>Button</ix-button>
<ix-button mode='danger'>Button</ix-button>
</ix-space>
</template>
```
10 changes: 10 additions & 0 deletions packages/components/space/demo/customSize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
order: 4
title:
zh: 自定义间距
en: Custom space size
---

## zh

自定义间距大小。
Loading

0 comments on commit 83ada1a

Please sign in to comment.