forked from opentiny/tiny-vue
feat: config-provider (#271)
* feat: config-provider * 🐞 fix(config-provider): 开发者如果只覆盖某个design item 那么其他的常量会被覆盖为 undefined. commit主要修复该问题 * 🧪 test(config-provider): 补充单元测试 * 🐞 fix(config-provider): 修改检视意见
This commit is contained in:
parent
6b1d103d69
commit
77b68bea18
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"attrs": [
|
||||
{
|
||||
"name": "design",
|
||||
"sample": "base",
|
||||
"desc": "规范",
|
||||
"type": "ConfigProviderProps",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "breakPoints",
|
||||
"sample": "base",
|
||||
"desc": "断点, 为Layout提供",
|
||||
"type": "breakPoint",
|
||||
"defaultValue": "{'xs': 480,'sm': 640,'md': 768,'lg': 1024,'xl': 1280,'2xl': 1536}"
|
||||
},
|
||||
{
|
||||
"name": "direction",
|
||||
"sample": "text-dire",
|
||||
"desc": "文字排版方向",
|
||||
"type": "'ltr' | 'rtl'",
|
||||
"defaultValue": "ltr"
|
||||
},
|
||||
{
|
||||
"name": "tag",
|
||||
"type": "object",
|
||||
"desc": "是否被元素包裹, 如果是vue2且没有一个单一节点, 组件会自动创建一个div",
|
||||
"sample": "base",
|
||||
"defaultValue": "{enable: true,name: 'div'}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
{
|
||||
"title": "基本使用",
|
||||
"content": "如何使用ConfigProvider组件",
|
||||
"link": "config-provider/base",
|
||||
"component": " ConfigProvide 组件",
|
||||
"findIntroStr": "",
|
||||
"demoId": "base"
|
||||
},
|
||||
{
|
||||
"title": "改变文字方向",
|
||||
"content": "有时, 我们需要改变文字的排列方向, 例如我们在排列阿拉伯语是就需要RTL而非LTR.config-provider也考虑到了这点,只需要覆写design.direction即可",
|
||||
"link": "config-provider/text-direct",
|
||||
"component": " ConfigProvide 组件",
|
||||
"findIntroStr": "",
|
||||
"demoId": "text-direct"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-config-provider :tag="tag">
|
||||
<tiny-tag>这是一个Tag</tiny-tag>
|
||||
<tiny-tag>{{ tagEnable }}</tiny-tag>
|
||||
</tiny-config-provider>
|
||||
<br />
|
||||
<div :style="[tag.enable && 'padding: 0px 1em;']">
|
||||
<tiny-switch v-model="tag.enable" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ConfigProvider, Tag, Switch } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyConfigProvider: ConfigProvider,
|
||||
TinyTag: Tag,
|
||||
TinySwitch: Switch
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tag: {
|
||||
enable: true,
|
||||
name: 'div'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagEnable() {
|
||||
return this.tag.enable ? '被DOM包裹' : '没有被DOM包裹'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tiny-config-provider{
|
||||
padding: 1em;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 1em;
|
||||
}
|
||||
.tiny-tag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-config-provider :direction="direction">
|
||||
<h1>Text</h1>
|
||||
<h1>المعرفة نور والجهل ظلام</h1>
|
||||
<div>
|
||||
<span>ما هو مكتوب في الكتب هو مجرد كلمات ، وما يتم تذكره هو المعرفة</span>
|
||||
</div>
|
||||
<hr />
|
||||
<h1>Input</h1>
|
||||
<tiny-input v-model="input" placeholder="الرجاء إدخال المحتوى"></tiny-input>
|
||||
<h1>Container</h1>
|
||||
<div class="content">
|
||||
<tiny-layout>
|
||||
<tiny-row>
|
||||
<tiny-col :span="12">
|
||||
<div class="col">
|
||||
span 12
|
||||
</div>
|
||||
</tiny-col>
|
||||
</tiny-row>
|
||||
<tiny-row>
|
||||
<tiny-col :span="3">
|
||||
<div class="col">
|
||||
span 3
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="6">
|
||||
<div class="col">
|
||||
span 6
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="3">
|
||||
<div class="col">
|
||||
span 3
|
||||
</div>
|
||||
</tiny-col>
|
||||
</tiny-row>
|
||||
<tiny-row>
|
||||
<tiny-col :span="2">
|
||||
<div class="col">
|
||||
span 2
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="3">
|
||||
<div class="col">
|
||||
span 3
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="2">
|
||||
<div class="col">
|
||||
span 2
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="3">
|
||||
<div class="col">
|
||||
span 3
|
||||
</div>
|
||||
</tiny-col>
|
||||
<tiny-col :span="2">
|
||||
<div class="col">
|
||||
span 2
|
||||
</div>
|
||||
</tiny-col>
|
||||
</tiny-row>
|
||||
</tiny-layout>
|
||||
</div>
|
||||
</tiny-config-provider>
|
||||
|
||||
<tiny-button @click="changeDirect('rtl')">
|
||||
RTL
|
||||
</tiny-button>
|
||||
<tiny-button @click="changeDirect('ltr')">
|
||||
LTR
|
||||
</tiny-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ConfigProvider, Button, Input, Layout, Row, Col } from '@opentiny/vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyConfigProvider: ConfigProvider,
|
||||
TinyButton: Button,
|
||||
TinyInput: Input,
|
||||
TinyLayout: Layout,
|
||||
TinyRow: Row,
|
||||
TinyCol: Col
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
direction: 'ltr',
|
||||
input: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeDirect(direct) {
|
||||
this.direction = direct
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content .tiny-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.content .tiny-row .last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content .tiny-col .col {
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: #1f9ed8;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.content .tiny-col:nth-child(even) .col {
|
||||
background: #73d0fc;
|
||||
}
|
||||
</style>
|
|
@ -1075,6 +1075,12 @@ const routerArr = [
|
|||
zh: '全屏显示',
|
||||
enSuffix: true,
|
||||
path: '/fullscreen'
|
||||
},
|
||||
{
|
||||
en: 'ConfigProvider',
|
||||
zh: '全局设置',
|
||||
enSuffix: true,
|
||||
path: '/config-provider'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright (c) 2022 - present TinyVue Authors.
|
||||
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Config-Provider
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2022 - present TinyVue Authors.
|
||||
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) 2022 - present TinyVue Authors.
|
||||
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import { mountPcMode } from '@opentiny-internal/vue-test-utils'
|
||||
import { describe, test, expect } from 'vitest'
|
||||
import ConfigProvider, { configProviderContextKey } from '@opentiny/vue-config-provider'
|
||||
import { defineComponent, h, inject } from 'vue'
|
||||
import type { ConfigProviderProps } from '../src/props'
|
||||
import { isVue2 } from '@opentiny/vue-common'
|
||||
|
||||
describe('PC Mode', () => {
|
||||
const mount = mountPcMode
|
||||
test('rtl', () => {
|
||||
const wrapper = mount(ConfigProvider, {
|
||||
props: {
|
||||
direction: 'rtl'
|
||||
}
|
||||
})
|
||||
expect(wrapper.classes()).toContain('tiny-config-provider--rtl')
|
||||
})
|
||||
describe('tag', () => {
|
||||
test('no tag', () => {
|
||||
const wrapper = mount(ConfigProvider, {
|
||||
props: {
|
||||
tag: {
|
||||
enable: false,
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
default: ['<button>just button</button>'],
|
||||
}
|
||||
})
|
||||
expect(wrapper.html()).not.contain('div')
|
||||
})
|
||||
test('has tag but not div', () => {
|
||||
const wrapper = mount(ConfigProvider, {
|
||||
props: {
|
||||
tag: {
|
||||
enable: true,
|
||||
name: 'custom-el'
|
||||
}
|
||||
}
|
||||
})
|
||||
const html = wrapper.html()
|
||||
expect(html).contain('custom-el')
|
||||
expect(html).not.contain('div')
|
||||
})
|
||||
test('mutil node', () => {
|
||||
const wrapper = mount(ConfigProvider, {
|
||||
props: {
|
||||
tag: {
|
||||
enable: false
|
||||
}
|
||||
},
|
||||
slots: {
|
||||
default: ['<button>btn1</button>', '<button>btn2</button>']
|
||||
}
|
||||
})
|
||||
if (isVue2) {
|
||||
expect(wrapper.html()).contain('div')
|
||||
expect(wrapper.classes().length).toBe(0)
|
||||
} else {
|
||||
expect(wrapper.html()).not.contain('div')
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!isVue2) {
|
||||
describe('inject', () => {
|
||||
test('one layer', () => {
|
||||
let component
|
||||
if (!isVue2) {
|
||||
component = defineComponent({
|
||||
name: 'CustomComponent',
|
||||
setup() {
|
||||
const config = inject<ConfigProviderProps>(configProviderContextKey)
|
||||
expect(config).not.toBeNull()
|
||||
expect(config).not.toStrictEqual({})
|
||||
expect(config?.direction).not.toBe('ltr')
|
||||
// just fix Component is missing template or render function.
|
||||
return () => h('div')
|
||||
}
|
||||
})
|
||||
}
|
||||
mount(ConfigProvider, {
|
||||
slots: {
|
||||
default: [component]
|
||||
},
|
||||
props: {
|
||||
direction: 'rtl'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
import type { ConfigProviderProps } from '../src/props'
|
||||
import { configProviderContextKey } from '../index'
|
||||
import { hooks } from '@opentiny/vue-common/src'
|
||||
|
||||
export function useConfig(): ConfigProviderProps | {} {
|
||||
return hooks.inject(configProviderContextKey) ?? {}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import ConfigProvider from './src/index.vue'
|
||||
import { version } from './package.json'
|
||||
|
||||
export const configProviderContextKey = Symbol('CONFIG_PROVIDER_CONTEXT_KEY')
|
||||
|
||||
/* istanbul ignore next */
|
||||
ConfigProvider.install = function (Vue) {
|
||||
Vue.component(ConfigProvider.name, ConfigProvider)
|
||||
|
|
|
@ -1,21 +1,102 @@
|
|||
<template>
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { $prefix, defineComponent, provideDesignConfig } from '@opentiny/vue-common'
|
||||
import { provideDesignConfig, type PropType, hooks, props as _props, isVue2 } from '@opentiny/vue-common'
|
||||
import type { Tag, TextDirection, breakPoint } from './props'
|
||||
import { $prefix, defineComponent } from '@opentiny/vue-common'
|
||||
import { configProviderContextKey } from '../index'
|
||||
|
||||
export default defineComponent({
|
||||
name: $prefix + 'ConfigProvider',
|
||||
props: {
|
||||
design: {
|
||||
type: Object
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
breakPoints: {
|
||||
type: Object as PropType<breakPoint>,
|
||||
default: () => {
|
||||
return {
|
||||
breakPoints: {
|
||||
'xs': 480,
|
||||
'sm': 640,
|
||||
'md': 768,
|
||||
'lg': 1024,
|
||||
'xl': 1280,
|
||||
'2xl': 1536
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
direction: {
|
||||
type: String as PropType<TextDirection>,
|
||||
default: 'ltr'
|
||||
},
|
||||
tag: {
|
||||
type: Object as PropType<Tag>,
|
||||
default: () => {
|
||||
return {
|
||||
enable: true,
|
||||
name: 'div'
|
||||
}
|
||||
}
|
||||
},
|
||||
..._props.map((item) => {
|
||||
return {
|
||||
[item]: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}).reduce((pre, cur) => {
|
||||
return {
|
||||
...pre,
|
||||
...cur
|
||||
}
|
||||
})
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const { direction } = hooks.toRefs(props)
|
||||
provideDesignConfig(props.design)
|
||||
const isRTL = hooks.computed(() => direction.value === 'rtl')
|
||||
const cssVar = hooks.computed(() => {
|
||||
return {
|
||||
'--text-direction': direction.value
|
||||
}
|
||||
})
|
||||
const classNames = hooks.reactive({
|
||||
'tiny-config-provider': true,
|
||||
'tiny-config-provider--rtl': isRTL
|
||||
})
|
||||
hooks.provide(configProviderContextKey, props)
|
||||
return {
|
||||
slots,
|
||||
classNames,
|
||||
cssVar,
|
||||
props,
|
||||
isVue2
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
provideDesignConfig(props.design)
|
||||
render() {
|
||||
const attr = {
|
||||
class: this.classNames,
|
||||
style: this.cssVar
|
||||
}
|
||||
if (!this.props.tag.enable) {
|
||||
const slots = this.slots?.default?.()
|
||||
if (isVue2 && (slots?.length ?? 1) > 1) {
|
||||
return hooks.h('div', {}, slots)
|
||||
}
|
||||
return slots
|
||||
}
|
||||
const tagName = this.props.tag.name ?? 'div'
|
||||
return hooks.h(tagName, attr, this.$slots.default)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tiny-config-provider--rtl{
|
||||
direction: var(--text-direction);
|
||||
transform: translateY(-1);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
export interface breakPoint {
|
||||
'xs': number
|
||||
'sm': number
|
||||
'md': number
|
||||
'lg': number
|
||||
'xl': number
|
||||
'2xl': number
|
||||
}
|
||||
export type TextDirection = 'rtl' | 'lrt'
|
||||
export interface Tag {
|
||||
enable: boolean
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface ConfigProviderProps {
|
||||
breakPoints: breakPoint
|
||||
direction: 'rtl' | 'ltr'
|
||||
globalPrefix?: string
|
||||
tag: {
|
||||
enable?: boolean
|
||||
name?: string
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue