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: '全屏显示',
|
zh: '全屏显示',
|
||||||
enSuffix: true,
|
enSuffix: true,
|
||||||
path: '/fullscreen'
|
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 ConfigProvider from './src/index.vue'
|
||||||
import { version } from './package.json'
|
import { version } from './package.json'
|
||||||
|
|
||||||
|
export const configProviderContextKey = Symbol('CONFIG_PROVIDER_CONTEXT_KEY')
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
ConfigProvider.install = function (Vue) {
|
ConfigProvider.install = function (Vue) {
|
||||||
Vue.component(ConfigProvider.name, ConfigProvider)
|
Vue.component(ConfigProvider.name, ConfigProvider)
|
||||||
|
|
|
@ -1,21 +1,102 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<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({
|
export default defineComponent({
|
||||||
name: $prefix + 'ConfigProvider',
|
name: $prefix + 'ConfigProvider',
|
||||||
props: {
|
props: {
|
||||||
design: {
|
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) {
|
render() {
|
||||||
provideDesignConfig(props.design)
|
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>
|
</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