Repository: IduxFE/components (ts)

Issue:
[components]support global config
- [x] I have searched the [issues](https://github.com/IduxFE/components/issues) of this repository and believe that this is not a duplicate.

### What problem does this feature solve?

### What does the proposed API look like?

```html
<!-- App.vue -->
<script lang="ts">
import { defineComponent } from 'vue'
import type { ButtonConfig } from '@idux/components'
import { useGlobalConfig } from '@idux/components'

export default defineComponent({
  setup() {
    // 设置 button 组件默认大小为 large
    const buttonConfig = useGlobalConfig('button',  { size: 'large'})

    // 可以动态修改全局配置
    const changeButtonConfig = () => {
      buttonConfig.size = 'small'
    }

    return { changeButtonConfig }
  }
})
</script>
```

Pull request:
feat(comp:core): support global config
fix #5

## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh-CN.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed


## PR Type
What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [ ] Other... Please describe:


## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->


## What is the new behavior?


## Other information



Patch:
diff --git a/commitlint.config.js b/commitlint.config.js
index 0b0fc73d..02b5b2e8 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -24,7 +24,14 @@ function getScopesRule() {
     return [2, 'always', scopes]
   }
   const { scope, type } = parsed
-  if (scope && !scopes.includes(scope) && type !== 'release' && !/cdk:.+/.test(scope) && !/component:.+/.test(scope)) {
+  if (
+    scope &&
+    !scopes.includes(scope) &&
+    type !== 'release' &&
+    !/cdk:.+/.test(scope) &&
+    !/comp:.+/.test(scope) &&
+    !/pro:.+/.test(scope)
+  ) {
     return [2, 'always', scopes]
   } else {
     return [2, 'always', []]
diff --git a/docs/contributing.zh-CN.md b/docs/contributing.zh-CN.md
index 0dacf494..aeebaee1 100644
--- a/docs/contributing.zh-CN.md
+++ b/docs/contributing.zh-CN.md
@@ -1,3 +1,8 @@
+---
+order: 11
+title: 贡献指南
+---
+
 # 贡献指南
 
 这篇指南会指导你如何为 `@idux` 贡献一份自己的力量，请在你要提 issue 或者 pull request 之前花几分钟来阅读一遍这篇指南。
diff --git a/docs/global-config.zh-CN.md b/docs/global-config.zh-CN.md
new file mode 100644
index 00000000..3a5fe7a8
--- /dev/null
+++ b/docs/global-config.zh-CN.md
@@ -0,0 +1,81 @@
+---
+order: 7
+title: 全局配置项
+---
+
+我们给众多组件添加了**全局配置**功能，你可以通过全局配置来定义组件的默认行为，从而减少在模板中需要写的代码（让你的代码更加清爽）。
+
+支持在运行时修改全局配置项，同时也支持局部覆盖全局配置项。
+
+## 如何使用
+
+如果你想要在全局范围内为某个组件提供默认配置项，请在根组件 `AppComponent` 中使用 `useGlobalConfig` 进行覆盖，例如：
+
+```html
+<!-- App.vue -->
+<script lang="ts">
+import { defineComponent } from 'vue'
+import type { ButtonConfig } from '@idux/components'
+import { useGlobalConfig } from '@idux/components'
+
+export default defineComponent({
+  setup() {
+    // 设置 button 组件默认大小为 large
+    const buttonConfig = useGlobalConfig('button',  { size: 'large'})
+
+    // 可以动态修改全局配置
+    const changeButtonConfig = () => {
+      buttonConfig.size = 'small'
+    }
+
+    return { changeButtonConfig }
+  }
+})
+</script>
+```
+
+同理，如果你只想要再某个业务组件内提供默认配置项，就在业务组件内使用 `useGlobalConfig` 进行覆盖，例如：
+
+```html
+<!-- Home.vue -->
+<template>
+  <ix-button @click="changeButtonConfig">{{ buttonConfig.size }} button</ix-button>
+  <ix-button size="large" @click="changeButtonConfig">The Button are always large</ix-button>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+import type { ButtonConfig } from '@idux/components'
+import { useGlobalConfig } from '@idux/components'
+
+export default defineComponent({
+  name: 'Home',
+  setup() {
+    // 设置 button 组件默认大小为 large, 只会影响当前组件及其子组件内的 button 大小
+    const buttonConfig = useGlobalConfig('button',  { size: 'large'})
+
+    // 同样支持动态修改全局配置
+    const changeButtonConfig = () => {
+      buttonConfig.size = 'small'
+    }
+
+    return { buttonConfig, changeButtonConfig }
+  }
+})
+</script>
+```
+
+## 全局配置项的优先级
+
+对于任何一个属性来说，各个来源的值的优先级如下：
+
+1. 为组件的某个实例单独设置的值(props)
+2. 业务组件(Home.vue)覆盖的全局配置
+3. 根组件(App.vue)覆盖的全局配置
+4. 组件提供的默认全局配置
+
+## 查看所有可用的全局配置项
+
+[`GlobalConfig`](https://github.com/IduxFE/components/blob/master/packages/components/core/config/types.ts) 接口提供的类型定义信息能够帮助你找到所有支持全局配置项的组件和属性。
+
+另外，每个组件的文档都会指出哪些属性可以通过全局配置项的方式指定。
diff --git a/packages/components/core/config/index.ts b/packages/components/core/config/index.ts
new file mode 100644
index 00000000..e2e27331
--- /dev/null
+++ b/packages/components/core/config/index.ts
@@ -0,0 +1,2 @@
+export * from './types'
+export * from './useGlobalConfig'
diff --git a/packages/components/core/config/types.ts b/packages/components/core/config/types.ts
new file mode 100644
index 00000000..4cbf0906
--- /dev/null
+++ b/packages/components/core/config/types.ts
@@ -0,0 +1,12 @@
+import type { ComponentSize, ButtonMode } from '../types'
+
+export type GlobalConfigKey = keyof GlobalConfig
+
+export interface GlobalConfig {
+  button: ButtonConfig
+}
+
+export interface ButtonConfig {
+  mode: ButtonMode
+  size: ComponentSize
+}
diff --git a/packages/components/core/config/useGlobalConfig.ts b/packages/components/core/config/useGlobalConfig.ts
new file mode 100644
index 00000000..c44edde2
--- /dev/null
+++ b/packages/components/core/config/useGlobalConfig.ts
@@ -0,0 +1,40 @@
+import { inject, shallowReactive, provide, readonly } from 'vue'
+import type { DeepReadonly } from 'vue'
+import type { ButtonConfig, GlobalConfig, GlobalConfigKey } from './types'
+
+const button: ButtonConfig = shallowReactive({ mode: 'default', size: 'medium' })
+
+const defaultConfig: GlobalConfig = {
+  button,
+}
+
+const globalConfigToken: Record<GlobalConfigKey, symbol> = {
+  button: Symbol(),
+}
+
+/**
+ *
+ * @param compName
+ * @param config optional, overrides the default global configuration within the current component scope
+ */
+export function useGlobalConfig<T extends GlobalConfigKey>(
+  compName: T,
+  config: Partial<GlobalConfig[T]>,
+): GlobalConfig[T]
+export function useGlobalConfig<T extends GlobalConfigKey>(compName: T): DeepReadonly<GlobalConfig[T]>
+export function useGlobalConfig<T extends GlobalConfigKey>(
+  compName: T,
+  config?: Partial<GlobalConfig[T]>,
+): GlobalConfig[T] | DeepReadonly<GlobalConfig[T]> {
+  const token = globalConfigToken[compName]
+  const parentConfig = inject(token, defaultConfig[compName])
+
+  if (!config) {
+    return readonly(parentConfig)
+  }
+
+  const currConfig: GlobalConfig[T] = shallowReactive({ ...parentConfig, ...config })
+  provide(token, currConfig)
+
+  return currConfig
+}
diff --git a/packages/components/core/types/index.ts b/packages/components/core/types/index.ts
new file mode 100644
index 00000000..b3dea1d0
--- /dev/null
+++ b/packages/components/core/types/index.ts
@@ -0,0 +1,3 @@
+export type ComponentSize = 'large' | 'medium' | 'small'
+
+export type ButtonMode = 'primary' | 'default' | 'dashed' | 'text' | 'link'
diff --git a/packages/components/core/utils/index.ts b/packages/components/core/utils/index.ts
new file mode 100644
index 00000000..777816aa
--- /dev/null
+++ b/packages/components/core/utils/index.ts
@@ -0,0 +1,1 @@
+export * from './installComponent'
diff --git a/packages/components/core/utils/installComponent.ts b/packages/components/core/utils/installComponent.ts
new file mode 100644
index 00000000..8fcc9b91
--- /dev/null
+++ b/packages/components/core/utils/installComponent.ts
@@ -0,0 +1,7 @@
+import type { App, DefineComponent } from 'vue'
+
+export const installComponent = (component: DefineComponent) => {
+  return (app: App): void => {
+    app.component(component.name, component)
+  }
+}


Test patch:
diff --git a/packages/components/core/config/__tests__/useGlobalConfig.spec.ts b/packages/components/core/config/__tests__/useGlobalConfig.spec.ts
new file mode 100644
index 00000000..9af83e2d
--- /dev/null
+++ b/packages/components/core/config/__tests__/useGlobalConfig.spec.ts
@@ -0,0 +1,38 @@
+import { mount } from '@vue/test-utils'
+import { ComponentOptions, nextTick } from 'vue'
+import { useGlobalConfig } from '../useGlobalConfig'
+
+const getComp = () => {
+  return {
+    template: `<div>{{ config.size }}</div>`,
+    setup() {
+      const config = useGlobalConfig('button')
+      return { config }
+    },
+  }
+}
+
+const getApp = (Comp: ComponentOptions) => {
+  return mount({
+    components: { Comp },
+    template: `<Comp />`,
+    setup() {
+      const config = useGlobalConfig('button', { size: 'large' })
+      const changeConfig = () => (config.size = 'small')
+      return { changeConfig }
+    },
+  })
+}
+
+describe('useGlobalConfig.ts', () => {
+  test('GlobalConfig work', async () => {
+    const wrapper = getApp(getComp())
+
+    expect(wrapper.text()).toEqual('large')
+
+    wrapper.vm.changeConfig()
+    await nextTick()
+
+    expect(wrapper.text()).toEqual('small')
+  })
+})