From 6177b96e86e4e4cda2eca3a5fcaeee50e4618db8 Mon Sep 17 00:00:00 2001
From: 07akioni <07akioni2@gmail.com>
Date: Fri, 11 Jun 2021 16:22:06 +0800
Subject: [PATCH] feat(button): unstable rtl (#74)

---
 src/_mixins/use-rtl.ts                        | 51 +++++++++++++++++++
 src/button/demos/zhCN/index.demo-entry.md     |  1 +
 src/button/demos/zhCN/rtl-debug.demo.md       | 24 +++++++++
 src/button/src/Button.tsx                     | 10 +++-
 src/button/src/styles/button-rtl.cssr.ts      |  7 +++
 src/button/styles/index.ts                    |  1 +
 src/button/styles/rtl.ts                      |  6 +++
 src/config-provider/src/ConfigProvider.ts     | 30 ++++++++---
 src/config-provider/src/internal-interface.ts | 12 +++++
 src/styles.ts                                 |  2 +-
 10 files changed, 136 insertions(+), 8 deletions(-)
 create mode 100644 src/_mixins/use-rtl.ts
 create mode 100644 src/button/demos/zhCN/rtl-debug.demo.md
 create mode 100644 src/button/src/styles/button-rtl.cssr.ts
 create mode 100644 src/button/styles/rtl.ts

diff --git a/src/_mixins/use-rtl.ts b/src/_mixins/use-rtl.ts
new file mode 100644
index 000000000..475beccbb
--- /dev/null
+++ b/src/_mixins/use-rtl.ts
@@ -0,0 +1,51 @@
+import { Ref, onBeforeMount, inject, watchEffect, computed } from 'vue'
+import { ssrInjectionKey } from '../ssr/context'
+import {
+  RtlEnabledState,
+  RtlItem
+} from '../config-provider/src/internal-interface'
+
+// The current implemention will take extra perf & memory usage. I just want to
+// make it work now. If we can determine whether the style is already mounted,
+// we won't need to watch effect. However, we need to make css-render support
+// it. We need to refactor ssrAdapter and expose a exists function
+export default function useRtl (
+  mountId: string,
+  rtlStateRef: Ref<RtlEnabledState | undefined> | undefined,
+  clsPrefixRef: Ref<string>
+): Ref<RtlItem | undefined> | undefined {
+  if (!rtlStateRef) return undefined
+  const ssrAdapter = inject(ssrInjectionKey, undefined)
+  const componentRtlStateRef = computed(() => {
+    const { value: rtlState } = rtlStateRef
+    if (!rtlState) {
+      return undefined
+    }
+    const componentRtlState = rtlState[mountId as keyof RtlEnabledState]
+    if (!componentRtlState) {
+      return undefined
+    }
+    return componentRtlState
+  })
+  const mountStyle = (): void => {
+    watchEffect(() => {
+      const { value: clsPrefix } = clsPrefixRef
+      const { value: componentRtlState } = componentRtlStateRef
+      if (!componentRtlState) return
+      componentRtlState.style.mount({
+        id: `${clsPrefix}${mountId}Rtl`,
+        head: true,
+        props: {
+          bPrefix: clsPrefix ? `.${clsPrefix}-` : undefined
+        },
+        ssr: ssrAdapter
+      })
+    })
+  }
+  if (ssrAdapter) {
+    mountStyle()
+  } else {
+    onBeforeMount(mountStyle)
+  }
+  return componentRtlStateRef
+}
diff --git a/src/button/demos/zhCN/index.demo-entry.md b/src/button/demos/zhCN/index.demo-entry.md
index 6bf35bed2..e4ae7b6dc 100644
--- a/src/button/demos/zhCN/index.demo-entry.md
+++ b/src/button/demos/zhCN/index.demo-entry.md
@@ -18,6 +18,7 @@ ghost
 loading
 color
 group
+rtl-debug
 debug
 ```
 
diff --git a/src/button/demos/zhCN/rtl-debug.demo.md b/src/button/demos/zhCN/rtl-debug.demo.md
new file mode 100644
index 000000000..7742d09ac
--- /dev/null
+++ b/src/button/demos/zhCN/rtl-debug.demo.md
@@ -0,0 +1,24 @@
+# Rtl Debug
+
+```html
+<n-space vertical>
+  <n-space><n-switch v-model:value="rtlEnabled" />Rtl</n-space>
+  <n-config-provider :rtl="rtlEnabled ? rtlStyles : undefined">
+    <n-button>Rtl Test</n-button>
+  </n-config-provider>
+</n-space>
+```
+
+```js
+import { defineComponent, ref } from 'vue'
+import { unstableButtonRtl } from 'naive-ui'
+
+export default defineComponent({
+  setup () {
+    return {
+      rtlEnabled: ref(false),
+      rtlStyles: [unstableButtonRtl]
+    }
+  }
+})
+```
diff --git a/src/button/src/Button.tsx b/src/button/src/Button.tsx
index 8ad312534..624c3ac1e 100644
--- a/src/button/src/Button.tsx
+++ b/src/button/src/Button.tsx
@@ -28,6 +28,7 @@ import type { ButtonTheme } from '../styles'
 import { buttonGroupInjectionKey } from './ButtonGroup'
 import type { Type, Size } from './interface'
 import style from './styles/button.cssr'
+import useRtl from '../../_mixins/use-rtl'
 
 const buttonProps = {
   ...(useTheme.props as ThemeProps<ButtonTheme>),
@@ -157,7 +158,7 @@ const Button = defineComponent({
     const handleBlur = (): void => {
       enterPressedRef.value = false
     }
-    const { mergedClsPrefixRef } = useConfig(props)
+    const { mergedClsPrefixRef, NConfigProvider } = useConfig(props)
     const themeRef = useTheme(
       'Button',
       'Button',
@@ -166,6 +167,11 @@ const Button = defineComponent({
       props,
       mergedClsPrefixRef
     )
+    const rtlEnabledRef = useRtl(
+      'Button',
+      NConfigProvider?.mergedRtlRef,
+      mergedClsPrefixRef
+    )
     return {
       selfRef,
       waveRef,
@@ -174,6 +180,7 @@ const Button = defineComponent({
       mergedSize: mergedSizeRef,
       showBorder: showBorderRef,
       enterPressed: enterPressedRef,
+      rtlEnabled: rtlEnabledRef,
       handleMouseDown,
       handleKeyDown,
       handleBlur,
@@ -390,6 +397,7 @@ const Button = defineComponent({
           `${mergedClsPrefix}-button`,
           `${mergedClsPrefix}-button--${this.type}-type`,
           {
+            [`${mergedClsPrefix}-button--rtl`]: this.rtlEnabled,
             [`${mergedClsPrefix}-button--disabled`]: this.disabled,
             [`${mergedClsPrefix}-button--block`]: this.block,
             [`${mergedClsPrefix}-button--pressed`]: this.enterPressed,
diff --git a/src/button/src/styles/button-rtl.cssr.ts b/src/button/src/styles/button-rtl.cssr.ts
new file mode 100644
index 000000000..fe4e06e55
--- /dev/null
+++ b/src/button/src/styles/button-rtl.cssr.ts
@@ -0,0 +1,7 @@
+import { cB, cM } from '../../../_utils/cssr'
+
+export default cB('button', [
+  cM('rtl', `
+    direction: rtl;
+  `)
+])
diff --git a/src/button/styles/index.ts b/src/button/styles/index.ts
index adaf8f71d..3acbdc8bc 100644
--- a/src/button/styles/index.ts
+++ b/src/button/styles/index.ts
@@ -1,3 +1,4 @@
 export { default as buttonDark } from './dark'
 export { default as buttonLight } from './light'
+export { default as buttonRtl } from './rtl'
 export type { ButtonThemeVars, ButtonTheme } from './light'
diff --git a/src/button/styles/rtl.ts b/src/button/styles/rtl.ts
new file mode 100644
index 000000000..9660f1f5b
--- /dev/null
+++ b/src/button/styles/rtl.ts
@@ -0,0 +1,6 @@
+import rtlStyle from '../src/styles/button-rtl.cssr'
+
+export default {
+  name: 'Button',
+  style: rtlStyle
+}
diff --git a/src/config-provider/src/ConfigProvider.ts b/src/config-provider/src/ConfigProvider.ts
index 466886b72..f316b1bc9 100644
--- a/src/config-provider/src/ConfigProvider.ts
+++ b/src/config-provider/src/ConfigProvider.ts
@@ -6,7 +6,9 @@ import {
   PropType,
   provide,
   InjectionKey,
-  renderSlot
+  renderSlot,
+  ComputedRef,
+  markRaw
 } from 'vue'
 import { useMemo } from 'vooks'
 import { merge } from 'lodash-es'
@@ -18,17 +20,18 @@ import type {
   GlobalComponentConfig,
   GlobalIconConfig
 } from './interface'
-import type { ConfigProviderInjection } from './internal-interface'
+import type {
+  ConfigProviderInjection,
+  RtlProp,
+  RtlEnabledState
+} from './internal-interface'
 import { NDateLocale, NLocale } from '../../locales'
 
 export const configProviderInjectionKey: InjectionKey<ConfigProviderInjection> =
   Symbol('configProviderInjection')
 
 export const configProviderProps = {
-  abstract: {
-    type: Boolean,
-    default: false
-  },
+  abstract: Boolean,
   bordered: {
     type: Boolean as PropType<boolean | undefined>,
     default: undefined
@@ -37,6 +40,7 @@ export const configProviderProps = {
   locale: Object as PropType<NLocale | null>,
   dateLocale: Object as PropType<NDateLocale | null>,
   namespace: String,
+  rtl: Array as PropType<RtlProp>,
   tag: {
     type: String,
     default: 'div'
@@ -160,7 +164,21 @@ export default defineComponent({
       const { clsPrefix } = props
       return NConfigProvider?.mergedClsPrefixRef.value ?? clsPrefix
     })
+    const mergedRtlRef: ComputedRef<RtlEnabledState | undefined> = computed(
+      () => {
+        const { rtl } = props
+        if (rtl === undefined) {
+          return NConfigProvider?.mergedRtlRef.value
+        }
+        const rtlEnabledState: RtlEnabledState = {}
+        for (const rtlInfo of rtl) {
+          rtlEnabledState[rtlInfo.name] = markRaw(rtlInfo)
+        }
+        return rtlEnabledState
+      }
+    )
     provide(configProviderInjectionKey, {
+      mergedRtlRef,
       mergedIconsRef,
       mergedComponentPropsRef,
       mergedBorderedRef,
diff --git a/src/config-provider/src/internal-interface.ts b/src/config-provider/src/internal-interface.ts
index 01250029d..1e02663df 100644
--- a/src/config-provider/src/internal-interface.ts
+++ b/src/config-provider/src/internal-interface.ts
@@ -1,4 +1,5 @@
 import { VNodeChild, Ref } from 'vue'
+import { CNode } from 'css-render'
 import type { AlertTheme } from '../../alert/styles'
 import type { AnchorTheme } from '../../anchor/styles'
 import type { AutoCompleteTheme } from '../../auto-complete/styles'
@@ -200,6 +201,16 @@ export interface GlobalIconConfig {
   zoomOut?: () => VNodeChild
 }
 
+export interface RtlItem {
+  name: keyof GlobalThemeWithoutCommon
+  style: CNode
+}
+export type RtlProp = RtlItem[]
+
+export type RtlEnabledState = Partial<
+Record<keyof GlobalThemeWithoutCommon, RtlItem>
+>
+
 export interface ConfigProviderInjection {
   mergedClsPrefixRef: Ref<string | undefined>
   mergedBorderedRef: Ref<boolean | undefined>
@@ -211,6 +222,7 @@ export interface ConfigProviderInjection {
   mergedIconsRef: Ref<GlobalIconConfig | undefined>
   mergedThemeRef: Ref<GlobalTheme | undefined>
   mergedThemeOverridesRef: Ref<GlobalThemeOverrides | undefined>
+  mergedRtlRef: Ref<RtlEnabledState | undefined>
   // deprecated
   /** @deprecated */
   mergedLegacyThemeRef: Ref<string | undefined>
diff --git a/src/styles.ts b/src/styles.ts
index b7336ec90..d599203e2 100644
--- a/src/styles.ts
+++ b/src/styles.ts
@@ -6,7 +6,7 @@ export { avatarDark } from './avatar/styles'
 export { backTopDark } from './back-top/styles'
 export { badgeDark } from './badge/styles'
 export { breadcrumbDark } from './breadcrumb/styles'
-export { buttonDark } from './button/styles'
+export { buttonDark, buttonRtl as unstableButtonRtl } from './button/styles'
 export { cardDark } from './card/styles'
 export { cascaderDark } from './cascader/styles'
 export { checkboxDark } from './checkbox/styles'