Skip to content

Commit

Permalink
feat(cdk:utils): add prop types util (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
danranVm authored Dec 30, 2020
1 parent 31105c5 commit 33551ff
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 100 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ module.exports = {
indent: ['error', 2, { SwitchCase: 1 }],
'object-curly-spacing': ['error', 'always'],
'arrow-parens': ['error', 'as-needed'],
'prettier/prettier': 'error',

// ts
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/member-delimiter-style': [
'error',
{
Expand All @@ -51,9 +51,13 @@ module.exports = {
],
'@typescript-eslint/no-empty-function': 'off',

// prettier
'prettier/prettier': 'error',

// vue
'vue/html-closing-bracket-spacing': 'error',
'vue/require-default-prop': 'error',
'vue/require-explicit-emits': 'error',
'vue/max-attributes-per-line': 'off',
'vue/attribute-hyphenation': 'off',
},
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ title: 贡献指南
- 文件命名:`PascalCase`
- props
- 命名:`camelCase`
- 默认值:如果是支持全局配置的 props, 则必须指定 `default: undefined`, 除了 `Boolean` 类型,其他类型都需要指定 `default`
- 默认值:请使用 `@idux/cdk/utils` 中的 `PropTypes`, 请注意:在没有显示的指定默认值的情况下,所有类型的默认值都为 `undefined`, 这与 vue compiler 默认的行为有所区别。
- slots
- 命名:`camelCase`
- 如果与某个 props 的功能一致时,需要跟该 props 同名
Expand Down
23 changes: 12 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,21 @@
"@types/gulp": "^4.0.0",
"@types/jest": "^26.0.0",
"@types/lodash": "^4.14.0",
"@types/marked": "^1.2.1",
"@types/parse5": "^5.0.3",
"@types/prismjs": "^1.16.2",
"@types/svgo": "^1.3.3",
"@types/marked": "^1.2.0",
"@types/parse5": "^5.0.0",
"@types/prismjs": "^1.16.0",
"@types/svgo": "^1.3.0",
"@types/yaml-front-matter": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.8.0",
"@typescript-eslint/parser": "^4.8.0",
"@vue/babel-plugin-jsx": "^1.0.0-rc.3",
"@vue/babel-plugin-jsx": "^1.0.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^2.0.0-beta.12",
"babel-jest": "^26.6.0",
"chalk": "^4.1.0",
"codecov": "^3.8.1",
"codecov": "^3.8.0",
"commitizen": "^4.2.0",
"cz-conventional-changelog": "^3.3.0",
"detect-port": "^1.3.0",
Expand All @@ -67,8 +67,8 @@
"lint-staged": "^10.5.0",
"lodash": "^4.17.0",
"markdownlint-cli": "^0.25.0",
"marked": "^1.2.5",
"parse5": "^6.0.1",
"marked": "^1.2.0",
"parse5": "^6.0.0",
"prettier": "^2.0.0",
"prismjs": "^1.22.0",
"remark": "^13.0.0",
Expand All @@ -77,15 +77,16 @@
"stylelint": "^13.0.0",
"stylelint-config-prettier": "^8.0.0",
"stylelint-config-standard": "^20.0.0",
"svgo": "^1.3.2",
"svgo": "^1.3.0",
"ts-jest": "^26.4.0",
"ts-node": "^9.1.0",
"tslib": "^2.0.3",
"tslib": "^2.0.0",
"typescript": "^4.1.0",
"vite": "^1.0.0-rc.13",
"vue": "^3.0.0",
"vue-jest": "^5.0.0-alpha.7",
"vue-router": "^4.0.1",
"vue-router": "^4.0.0",
"vue-types": "^3.0.0",
"yaml-front-matter": "^4.1.0"
},
"husky": {
Expand Down
41 changes: 41 additions & 0 deletions packages/cdk/utils/__tests__/propTypes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */

import { PropTypes } from '../propTypes'

describe('propTypes.ts', () => {
test('maxLength work', async () => {
const maxLength = PropTypes.maxLength(4)
expect(maxLength.default).toEqual(undefined)

const validator = maxLength.validator!.bind(undefined)

expect(validator('test')).toEqual(true)
expect(validator('test1')).toEqual(false)
})

test('minLength work', async () => {
const minLength = PropTypes.minLength(4)
expect(minLength.default).toEqual(undefined)

const validator = minLength.validator!.bind(undefined)

expect(validator('test')).toEqual(true)
expect(validator('tes')).toEqual(false)
})

test('positive work', async () => {
const positive = PropTypes.positive
expect(positive.default).toEqual(undefined)

const validator = positive.validator!.bind(undefined)

expect(validator(1)).toEqual(true)
expect(validator(0)).toEqual(false)
expect(validator(-1)).toEqual(false)
})

test('bool work', async () => {
const bool = PropTypes.bool
expect(bool.default).toEqual(undefined)
})
})
1 change: 1 addition & 0 deletions packages/cdk/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './convert'
export * from './propTypes'
export * from './typeof'
44 changes: 44 additions & 0 deletions packages/cdk/utils/propTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import VueTypes, { toType, toValidableType, VueTypeDef, VueTypeValidableDef } from 'vue-types'

// use `undefined` as the default value for each prop
VueTypes.sensibleDefaults = {}

export class PropTypes extends VueTypes {
// define a custom validator that accepts configuration parameters
static maxLength(max: number): VueTypeDef<string> {
return toType('maxLength', {
type: String,
validator: (value: string) => value.length <= max,
})
}

static minLength(min: number): VueTypeDef<string> {
return toType('minLength', {
type: String,
validator: (value: string) => value.length >= min,
})
}

// a native-like validator that supports the `.validable` method
static get positive(): VueTypeValidableDef<number> {
return toValidableType('positive', {
type: Number,
validator: (value: number) => value > 0,
})
}

// see https://github.com/dwightjack/vue-types/issues/71
static get bool(): VueTypeValidableDef<boolean> & { default: boolean } {
return toValidableType('bool', {
type: Boolean,
default: undefined,
}) as VueTypeValidableDef<boolean> & { default: boolean }
}
}

// withUndefined(PropTypes.oneOfType([PropTypes.bool, PropTypes.string])) is work
// see https://github.com/dwightjack/vue-types/issues/71
export function withUndefined<T extends { default?: unknown }>(type: T): T {
type.default = undefined
return type
}
18 changes: 10 additions & 8 deletions packages/components/badge/src/Badge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@
</span>
</template>
<script lang="ts">
import { computed, ComputedRef, defineComponent, onUpdated, reactive } from 'vue'
import { BadgeProps, SlotsExist } from './types'
import { computed, defineComponent, onUpdated, reactive } from 'vue'
import { isNumber, PropTypes } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/core/config'
import { isNumber } from '@idux/cdk/utils'
import type { ComputedRef } from 'vue'
import type { BadgeProps, SlotsExist } from './types'
export default defineComponent({
name: 'IxBadge',
props: {
count: { type: [Number, String], default: 0 },
showZero: { type: Boolean, default: undefined },
overflowCount: { type: [Number, String], default: undefined },
dot: { type: Boolean, default: undefined },
color: { type: String, default: undefined },
count: PropTypes.oneOfType([Number, String]).def(0),
showZero: PropTypes.bool,
overflowCount: PropTypes.oneOfType([Number, String]),
dot: PropTypes.bool,
color: PropTypes.string,
},
setup(props: BadgeProps, { slots }) {
const badgeConfig = useGlobalConfig('badge')
Expand Down
25 changes: 13 additions & 12 deletions packages/components/button/src/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,29 @@

<script lang="ts">
import { computed, defineComponent, inject, onUpdated, ref } from 'vue'
import { PropTypes } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/core/config'
import { IxIcon } from '@idux/components/icon'
import { buttonGroupInjectionKey } from './button'
import type { ComputedRef, PropType, Ref } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import type { ButtonConfig } from '@idux/components/core/config'
import type { ButtonMode, ComponentSize } from '@idux/components/core/types'
import type { ButtonGroupProps, ButtonProps, ButtonShape } from './types'
import type { ButtonMode } from '@idux/components/core/types'
import type { ButtonGroupProps, ButtonProps } from './types'
export default defineComponent({
name: 'IxButton',
components: { IxIcon },
props: {
mode: { type: String as PropType<ButtonMode>, default: undefined },
danger: Boolean,
ghost: Boolean,
disabled: Boolean,
loading: Boolean,
size: { type: String as PropType<ComponentSize>, default: undefined },
shape: { type: String as PropType<ButtonShape>, default: undefined },
block: Boolean,
icon: { type: String, default: undefined },
mode: PropTypes.oneOf(['primary', 'default', 'dashed', 'text', 'link'] as const),
danger: PropTypes.bool,
ghost: PropTypes.bool,
disabled: PropTypes.bool,
loading: PropTypes.bool,
size: PropTypes.oneOf(['large', 'medium', 'small'] as const),
shape: PropTypes.oneOf(['circle', 'round'] as const),
block: PropTypes.bool,
icon: PropTypes.string,
},
setup(props: ButtonProps, { slots }) {
const groupProps = inject(buttonGroupInjectionKey, {})
Expand Down
13 changes: 6 additions & 7 deletions packages/components/button/src/ButtonGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
</template>

<script lang="ts">
import type { PropType } from 'vue'
import type { ButtonMode, ComponentSize } from '@idux/components/core/types'
import type { ButtonGroupProps, ButtonShape } from './types'
import { defineComponent, provide } from 'vue'
import { PropTypes } from '@idux/cdk/utils'
import { buttonGroupInjectionKey } from './button'
import type { ButtonGroupProps } from './types'
export default defineComponent({
name: 'IxButtonGroup',
props: {
mode: { type: String as PropType<ButtonMode>, default: undefined },
size: { type: String as PropType<ComponentSize>, default: undefined },
shape: { type: String as PropType<ButtonShape>, default: undefined },
mode: PropTypes.oneOf(['primary', 'default', 'dashed', 'text', 'link'] as const),
size: PropTypes.oneOf(['large', 'medium', 'small'] as const),
shape: PropTypes.oneOf(['circle', 'round'] as const),
},
setup(props: ButtonGroupProps) {
provide(buttonGroupInjectionKey, props)
Expand Down
10 changes: 5 additions & 5 deletions packages/components/button/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type { ButtonMode, ComponentSize } from '@idux/components/core/types'
export type ButtonShape = 'circle' | 'round'
export interface ButtonProps {
readonly mode?: ButtonMode
readonly danger: boolean
readonly ghost: boolean
readonly disabled: boolean
readonly loading: boolean
readonly danger?: boolean
readonly ghost?: boolean
readonly disabled?: boolean
readonly loading?: boolean
readonly size?: ComponentSize
readonly shape?: ButtonShape
readonly block: boolean
readonly block?: boolean
readonly icon?: string
}

Expand Down
23 changes: 8 additions & 15 deletions packages/components/divider/src/Divider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,20 @@
</div>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import type { DividerConfig } from '@idux/components/core/config'
import type { DividerPosition, DividerType } from '@idux/components/core/types'
import type { DividerProps } from './types'
import { computed, defineComponent } from 'vue'
import { PropTypes } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/core/config'
import type { DividerConfig } from '@idux/components/core/config'
import type { DividerProps } from './types'
export default defineComponent({
name: 'IxDivider',
props: {
dashed: { type: Boolean, default: undefined },
plain: { type: Boolean, default: undefined },
position: {
type: String as PropType<DividerPosition>,
default: undefined,
},
type: {
type: String as PropType<DividerType>,
default: undefined,
},
dashed: PropTypes.bool,
plain: PropTypes.bool,
position: PropTypes.oneOf(['left', 'center', 'right'] as const),
type: PropTypes.oneOf(['horizontal', 'vertical'] as const),
},
setup(props: DividerProps, { slots }) {
const dividerConfig = useGlobalConfig('divider')
Expand Down
17 changes: 9 additions & 8 deletions packages/components/icon/src/Icon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
</component>
</template>
<script lang="ts">
import type { Ref, SetupContext } from 'vue'
import type { IconConfig } from '@idux/components/core/config'
import type { IconProps } from './types'
import { computed, defineComponent, onMounted, onUpdated, ref, watch } from 'vue'
import { isNumber, PropTypes, withUndefined } from '@idux/cdk/utils'
import { useGlobalConfig } from '@idux/components/core/config'
import { clearSVGElement, loadIconFontSvgElement, loadSVGElement } from './utils'
import type { Ref, SetupContext } from 'vue'
import type { IconConfig } from '@idux/components/core/config'
import type { IconProps } from './types'
export default defineComponent({
name: 'IxIcon',
props: {
name: { type: String, default: undefined },
rotate: { type: [Boolean, Number, String], default: false },
iconfont: Boolean,
name: PropTypes.string,
rotate: withUndefined(PropTypes.oneOfType([Boolean, Number, String])),
iconfont: PropTypes.bool,
},
setup(props: IconProps, { attrs }: SetupContext) {
const root = ref((null as unknown) as HTMLElement)
Expand Down Expand Up @@ -70,7 +71,7 @@ async function appendChild(props: IconProps, iconConfig: IconConfig, root: Ref<H
}
function handleRotate(svg: SVGElement, rotate?: number | string | boolean): void {
if (typeof rotate === 'number' || typeof rotate === 'string') {
if (isNumber(rotate)) {
svg.setAttribute('style', `transform: rotate(${rotate}deg)`)
} else {
svg.removeAttribute('style')
Expand Down
Loading

0 comments on commit 33551ff

Please sign in to comment.