Repository: ant-design/ant-design-mobile (ts)

Issue:
NumberKeyboard 组件支持两个自定义按键
### Version of antd-mobile

5.0.0-rc.7

### What is this feature about?

![image](https://user-images.githubusercontent.com/8766034/146906098-9b392530-6299-4951-ac34-4334534fa2fb.png)

`NumberKeyboard` 这个组件能否支持一下最多自定义两个按键呢，现在的组件没法同时支持输入负数+小数。

Pull request description:
feat(NumberKeyboard): support multiple customKeys
Close #4588

Patch:
diff --git a/src/components/number-keyboard/demos/demo1.tsx b/src/components/number-keyboard/demos/demo1.tsx
index 1a4f0958d..6ecaca1a4 100644
--- a/src/components/number-keyboard/demos/demo1.tsx
+++ b/src/components/number-keyboard/demos/demo1.tsx
@@ -42,10 +42,7 @@ export default () => {
           <List.Item onClick={() => openKeyboard('demo3')}>
             带确认键盘
           </List.Item>
-          <List.Item onClick={() => openKeyboard('demo4')}>
-            带自定义键盘
-          </List.Item>
-          <List.Item onClick={() => openKeyboard('demo5')}>乱序键盘</List.Item>
+          <List.Item onClick={() => openKeyboard('demo4')}>乱序键盘</List.Item>
           <List.Item
             onClick={e => {
               Dialog.alert({
@@ -53,7 +50,7 @@ export default () => {
                   <Button
                     color='primary'
                     fill='outline'
-                    onClick={() => openKeyboard('demo6')}
+                    onClick={() => openKeyboard('demo5')}
                   >
                     打开键盘
                   </Button>
@@ -65,7 +62,7 @@ export default () => {
           >
             弹窗内展示键盘
           </List.Item>
-          <List.Item onClick={() => openKeyboard('demo7')} arrow={false}>
+          <List.Item onClick={() => openKeyboard('demo6')} arrow={false}>
             {/* 添加 readOnly 阻止原生键盘弹出 */}
             <Input placeholder='请输入内容' value={value} readOnly />
           </List.Item>
@@ -98,27 +95,18 @@ export default () => {
         onClose={actions.onClose}
         onInput={actions.onInput}
         onDelete={actions.onDelete}
-        customKey='.'
-        showCloseButton={false}
-        confirmText='确定'
-      />
-      <NumberKeyboard
-        visible={visible === 'demo5'}
-        onClose={actions.onClose}
-        onInput={actions.onInput}
-        onDelete={actions.onDelete}
         randomOrder
         customKey='X'
         confirmText='确定'
       />
       <NumberKeyboard
-        visible={visible === 'demo6'}
+        visible={visible === 'demo5'}
         onClose={actions.onClose}
         onInput={actions.onInput}
         onDelete={actions.onDelete}
       />
       <NumberKeyboard
-        visible={visible === 'demo7'}
+        visible={visible === 'demo6'}
         onClose={actions.onClose}
         onInput={onInput}
         onDelete={onDelete}
diff --git a/src/components/number-keyboard/demos/demo2.tsx b/src/components/number-keyboard/demos/demo2.tsx
new file mode 100644
index 000000000..406a94f1e
--- /dev/null
+++ b/src/components/number-keyboard/demos/demo2.tsx
@@ -0,0 +1,58 @@
+import React, { useState } from 'react'
+import { List, NumberKeyboard } from 'antd-mobile'
+import { DemoBlock } from 'demos'
+
+export default () => {
+  const [visible, setVisible] = useState<string>('')
+
+  const openKeyboard = (name: string) => {
+    setVisible(name)
+  }
+
+  const onClose = () => {
+    setVisible('')
+  }
+
+  return (
+    <>
+      <DemoBlock title='自定义按钮' padding='0'>
+        <List>
+          <List.Item onClick={() => openKeyboard('demo1')}>
+            自定义键盘
+          </List.Item>
+          <List.Item onClick={() => openKeyboard('demo2')}>
+            多个自定义键盘
+          </List.Item>
+          <List.Item onClick={() => openKeyboard('demo3')}>
+            带确认键盘
+          </List.Item>
+          <List.Item onClick={() => openKeyboard('demo4')}>
+            多个自定义带确认键盘
+          </List.Item>
+        </List>
+      </DemoBlock>
+      <NumberKeyboard
+        visible={visible === 'demo1'}
+        onClose={onClose}
+        customKey={'-'}
+      />
+      <NumberKeyboard
+        visible={visible === 'demo2'}
+        onClose={onClose}
+        customKey={['-', '.']}
+      />
+      <NumberKeyboard
+        visible={visible === 'demo3'}
+        onClose={onClose}
+        customKey={'-'}
+        confirmText='确定'
+      />
+      <NumberKeyboard
+        visible={visible === 'demo4'}
+        onClose={onClose}
+        customKey={['-', '.']}
+        confirmText='确定'
+      />
+    </>
+  )
+}
diff --git a/src/components/number-keyboard/index.en.md b/src/components/number-keyboard/index.en.md
index 1c96b536d..8c286c5cb 100644
--- a/src/components/number-keyboard/index.en.md
+++ b/src/components/number-keyboard/index.en.md
@@ -14,6 +14,8 @@ If possible, we recommend using the native keyboard provided by the system or cl
 
 <code src="./demos/demo1.tsx"></code>
 
+<code src="./demos/demo2.tsx"></code>
+
 ## NumberKeyboard
 
 ### Props
@@ -24,7 +26,7 @@ If possible, we recommend using the native keyboard provided by the system or cl
 | afterShow       | Callback when the keyboard is completely bounced                                                                            | `() => void`                               | -                     |
 | closeOnConfirm  | Whether to automatically close when the ok button is clicked                                                                | `boolean`                                  | `true`                |
 | confirmText     | The text of the confirm button, if `null` is set, it would be shown                                                         | `string \| null`                           | `null`                |
-| customKey       | Customized button                                                                                                           | `'-' \| '.' \| 'X'`                        | -                     |
+| customKey       | Customized button                                                                                                           | `string \| [string, string]`               | -                     |
 | destroyOnClose  | Unmount content when not visible                                                                                            | `boolean`                                  | `false`               |
 | forceRender     | Render content forcely                                                                                                      | `boolean`                                  | `false`               |
 | getContainer    | To get the specified mounted HTML node, the default is `body`, if `null` returned, it would be rendered to the current node | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` |
diff --git a/src/components/number-keyboard/index.zh.md b/src/components/number-keyboard/index.zh.md
index 8c8cb74b2..669cee371 100644
--- a/src/components/number-keyboard/index.zh.md
+++ b/src/components/number-keyboard/index.zh.md
@@ -14,6 +14,8 @@
 
 <code src="./demos/demo1.tsx"></code>
 
+<code src="./demos/demo2.tsx"></code>
+
 ## NumberKeyboard
 
 ### 属性
@@ -24,7 +26,7 @@
 | afterShow       | 键盘完全弹出回调                                                          | `() => void`                               | -                     |
 | closeOnConfirm  | 是否在点击确定按钮时自动关闭                                              | `boolean`                                  | `true`                |
 | confirmText     | 完成按钮文案，`null` 不展示                                               | `string \| null`                           | `null`                |
-| customKey       | 自定义按钮                                                                | `'-' \| '.' \| 'X'`                        | -                     |
+| customKey       | 自定义按钮                                                                | `string \| [string, string]`               | -                     |
 | destroyOnClose  | 不可见时卸载内容                                                          | `boolean`                                  | `false`               |
 | forceRender     | 强制渲染内容                                                              | `boolean`                                  | `false`               |
 | getContainer    | 指定挂载的 HTML 节点，默认为 `body`，如果为 `null` 的话，会渲染到当前节点 | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` |
diff --git a/src/components/number-keyboard/number-keyboard.tsx b/src/components/number-keyboard/number-keyboard.tsx
index e76a6da7b..c374a1957 100644
--- a/src/components/number-keyboard/number-keyboard.tsx
+++ b/src/components/number-keyboard/number-keyboard.tsx
@@ -14,7 +14,7 @@ export type NumberKeyboardProps = {
   visible?: boolean
   title?: string
   confirmText?: string | null
-  customKey?: '-' | '.' | 'X'
+  customKey?: string | [string, string]
   randomOrder?: boolean
   showCloseButton?: boolean
   onInput?: (v: string) => void
@@ -63,12 +63,16 @@ export const NumberKeyboard: React.FC<NumberKeyboardProps> = p => {
   const keys = useMemo(() => {
     const defaultKeys = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
     const keyList = randomOrder ? shuffle(defaultKeys) : defaultKeys
+    const customKeys = Array.isArray(customKey) ? customKey : [customKey]
     keyList.push('0')
     if (confirmText) {
-      keyList.push(customKey || '')
+      if (customKeys.length === 2) {
+        keyList.splice(9, 0, customKeys.pop())
+      }
+      keyList.push(customKeys[0] || '')
     } else {
-      keyList.splice(9, 0, customKey || '')
-      keyList.push('BACKSPACE')
+      keyList.splice(9, 0, customKeys[0] || '')
+      keyList.push(customKeys[1] || 'BACKSPACE')
     }
     return keyList
   }, [customKey, confirmText, randomOrder, randomOrder && visible])
@@ -91,7 +95,6 @@ export const NumberKeyboard: React.FC<NumberKeyboardProps> = p => {
     clearInterval(intervalRef.current)
   }
 
-  // 点击键盘按键
   const onKeyPress = (
     e: TouchEvent<HTMLDivElement> | MouseEvent<HTMLDivElement>,
     key: string
@@ -109,13 +112,12 @@ export const NumberKeyboard: React.FC<NumberKeyboardProps> = p => {
         }
         break
       default:
-        // 当 customKey 不存在时，点击该键不应该触发 onInput
+        // onInput should't be called when customKey doesn't exist
         if (key !== '') onInput?.(key)
         break
     }
   }
 
-  // 渲染 title 和 close button
   const renderHeader = () => {
     if (!showCloseButton && !title) return null
     return (
@@ -141,13 +143,13 @@ export const NumberKeyboard: React.FC<NumberKeyboardProps> = p => {
     )
   }
 
-  // 渲染基础键盘按键
   const renderKey = (key: string, index: number) => {
     const isNumberKey = /^\d$/.test(key)
     const className = classNames(`${classPrefix}-key`, {
       [`${classPrefix}-key-number`]: isNumberKey,
       [`${classPrefix}-key-sign`]: !isNumberKey && key,
-      [`${classPrefix}-key-mid`]: index === 9 && !!confirmText,
+      [`${classPrefix}-key-mid`]:
+        index === 9 && !!confirmText && keys.length < 12,
     })
 
     const ariaProps = key


Test patch:
diff --git a/src/components/number-keyboard/tests/number-keyboard.test.tsx b/src/components/number-keyboard/tests/number-keyboard.test.tsx
index d07c0d844..ca7e8c73c 100644
--- a/src/components/number-keyboard/tests/number-keyboard.test.tsx
+++ b/src/components/number-keyboard/tests/number-keyboard.test.tsx
@@ -81,6 +81,7 @@ describe('NumberKeyboard', () => {
       `${classPrefix}-key-extra ${classPrefix}-key-ok`
     )
     expect(del).toHaveClass(`${classPrefix}-key-extra ${classPrefix}-key-bs`)
+    expect(screen.getByTitle('0')).toHaveClass(`${classPrefix}-key-mid`)
 
     mockClick(confirm)
     expect(onConfirm).toBeCalled()
@@ -133,4 +134,29 @@ describe('NumberKeyboard', () => {
     expect(onDelete).toBeCalledTimes(2)
     jest.useRealTimers()
   })
+
+  test('render with multiple customKeys', () => {
+    const onInput = jest.fn()
+    render(<NumberKeyboard customKey={['-', '.']} visible onInput={onInput} />)
+    const left = screen.getByText('-')
+    const right = screen.getByText('.')
+    expect(left).toBeInTheDocument()
+    expect(right).toBeInTheDocument()
+
+    mockClick(left)
+    expect(onInput).toBeCalledWith('-')
+    mockClick(right)
+    expect(onInput).toBeCalledWith('.')
+  })
+
+  test('render with multiple customKeys and confirmText', () => {
+    render(
+      <NumberKeyboard customKey={['-', '.']} visible confirmText='confirm' />
+    )
+    const left = screen.getByTitle('-')
+    const right = screen.getByTitle('.')
+    expect(left).toBeInTheDocument()
+    expect(right).toBeInTheDocument()
+    expect(screen.getByTitle('0')).not.toHaveClass(`${classPrefix}-key-mid`)
+  })
 })