From d7ba98fe8a6065aa8d5bf5e50a92dfe3bd12ab7b Mon Sep 17 00:00:00 2001 From: Guo Yunhe Date: Mon, 8 Apr 2024 20:20:53 +0800 Subject: [PATCH] feat(ConfigProvider): add icons config --- src/components/center-popup/center-popup.tsx | 7 +- src/components/check-list/check-list.tsx | 12 +++- src/components/collapse/collapse.tsx | 6 +- .../config-provider/config-provider.tsx | 67 +++++++++++++++++-- src/components/config-provider/index.en.md | 65 ++++++++++++++++++ src/components/config-provider/index.zh.md | 32 +++++++++ .../config-provider.test.tsx.snap | 38 +++++------ src/components/dropdown/item.tsx | 21 ++++-- src/components/form/form-item.tsx | 12 ++-- src/components/form/index.en.md | 1 + src/components/form/index.zh.md | 1 + .../image-uploader/image-uploader.less | 2 +- .../image-uploader/image-uploader.tsx | 13 ++-- src/components/image-uploader/index.en.md | 1 + src/components/image-uploader/index.zh.md | 1 + .../image-uploader.test.tsx.snap | 8 +-- src/components/input/index.en.md | 1 + src/components/input/index.zh.md | 1 + src/components/input/input.tsx | 17 +++-- src/components/list/list-item.tsx | 4 +- src/components/nav-bar/nav-bar.tsx | 5 +- src/components/nav-bar/tests/nav-bar.test.tsx | 20 ++++++ src/components/notice-bar/index.en.md | 1 + src/components/notice-bar/index.zh.md | 1 + src/components/notice-bar/notice-bar.less | 3 - src/components/notice-bar/notice-bar.tsx | 9 ++- .../notice-bar/tests/notice-bar.test.tsx | 2 +- src/components/number-keyboard/index.en.md | 2 + src/components/number-keyboard/index.zh.md | 2 + .../number-keyboard/number-keyboard.tsx | 19 ++++-- src/components/popup/index.en.md | 1 + src/components/popup/index.zh.md | 1 + ...pup-base-props.ts => popup-base-props.tsx} | 5 +- src/components/popup/popup.tsx | 7 +- src/components/result-page/result-page.tsx | 22 ++---- src/components/result/result-icon.tsx | 38 +++++++++++ src/components/result/result.tsx | 21 ++---- .../result/tests/result-icon.test.tsx | 33 +++++++++ src/components/search-bar/search-bar.tsx | 3 +- src/components/stepper/index.en.md | 2 + src/components/stepper/index.zh.md | 2 + src/components/stepper/stepper.tsx | 14 ++-- src/components/toast/tests/toast.test.tsx | 8 +-- src/components/toast/toast.less | 5 -- src/components/toast/toast.tsx | 10 ++- .../virtual-input/virtual-input.tsx | 12 ++-- src/utils/with-default-props.tsx | 1 + 47 files changed, 422 insertions(+), 137 deletions(-) create mode 100644 src/components/nav-bar/tests/nav-bar.test.tsx rename src/components/popup/{popup-base-props.ts => popup-base-props.tsx} (87%) create mode 100644 src/components/result/result-icon.tsx create mode 100644 src/components/result/tests/result-icon.test.tsx diff --git a/src/components/center-popup/center-popup.tsx b/src/components/center-popup/center-popup.tsx index 193600c029..4fcbf848d9 100644 --- a/src/components/center-popup/center-popup.tsx +++ b/src/components/center-popup/center-popup.tsx @@ -11,11 +11,11 @@ import classNames from 'classnames' import { NativeProps, withNativeProps } from '../../utils/native-props' import { ShouldRender } from '../../utils/should-render' import { useLockScroll } from '../../utils/use-lock-scroll' -import { CloseOutline } from 'antd-mobile-icons' import { defaultPopupBaseProps, PopupBaseProps, } from '../popup/popup-base-props' +import { useConfig } from '../config-provider' const classPrefix = 'adm-center-popup' @@ -38,7 +38,8 @@ const defaultProps = { } export const CenterPopup: FC = p => { - const props = mergeProps(defaultProps, p) + const { popup: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const unmountedRef = useUnmountedRef() const style = useSpring({ @@ -134,7 +135,7 @@ export const CenterPopup: FC = p => { props.onClose?.() }} > - + {props.closeIcon} )} {body} diff --git a/src/components/check-list/check-list.tsx b/src/components/check-list/check-list.tsx index c83efe1365..a49d7b17ad 100644 --- a/src/components/check-list/check-list.tsx +++ b/src/components/check-list/check-list.tsx @@ -1,11 +1,12 @@ import React from 'react' import type { FC, ReactNode } from 'react' +import { CheckOutline } from 'antd-mobile-icons' import { NativeProps, withNativeProps } from '../../utils/native-props' import List, { ListProps } from '../list' import { mergeProps } from '../../utils/with-default-props' import { CheckListContext } from './context' import { usePropsValue } from '../../utils/use-props-value' -import { CheckOutline } from 'antd-mobile-icons' +import { useConfig } from '../config-provider' const classPrefix = 'adm-check-list' @@ -26,11 +27,16 @@ export type CheckListProps = Pick & { const defaultProps = { multiple: false, defaultValue: [], - activeIcon: , } export const CheckList: FC = p => { - const props = mergeProps(defaultProps, p) + const { checkList: componentConfig = {} } = useConfig() + const props = mergeProps( + { activeIcon: }, + componentConfig, + defaultProps, + p + ) const [value, setValue] = usePropsValue(props) diff --git a/src/components/collapse/collapse.tsx b/src/components/collapse/collapse.tsx index 569cb3dc4a..1476b1c2fc 100644 --- a/src/components/collapse/collapse.tsx +++ b/src/components/collapse/collapse.tsx @@ -10,6 +10,7 @@ import { useMount } from 'ahooks' import { useShouldRender } from '../../utils/should-render' import { useIsomorphicUpdateLayoutEffect } from '../../utils/use-isomorphic-update-layout-effect' import { traverseReactNode } from '../../utils/traverse-react-node' +import { useConfig } from '../config-provider' const classPrefix = `adm-collapse` @@ -121,6 +122,7 @@ export type CollapseProps = ( } & NativeProps export const Collapse: FC = props => { + const { collapse: componentConfig = {} } = useConfig() const panels: ReactElement[] = [] traverseReactNode(props.children, child => { if (!isValidElement(child)) return @@ -197,7 +199,9 @@ export const Collapse: FC = props => { } const renderArrow = () => { - let arrow: CollapseProps['arrow'] = + let arrow: CollapseProps['arrow'] = componentConfig?.arrow || ( + + ) if (props.arrow !== undefined) { arrow = props.arrow } diff --git a/src/components/config-provider/config-provider.tsx b/src/components/config-provider/config-provider.tsx index f608e45f5a..38f20d1b18 100644 --- a/src/components/config-provider/config-provider.tsx +++ b/src/components/config-provider/config-provider.tsx @@ -2,9 +2,65 @@ import React, { useContext } from 'react' import type { FC, ReactNode } from 'react' import { Locale } from '../../locales/base' import zhCN from '../../locales/zh-CN' +import { mergeProps } from '../../utils/with-default-props' type Config = { locale: Locale + checkList?: { + activeIcon?: ReactNode + } + collapse?: { + arrow?: ReactNode | ((active: boolean) => ReactNode) + } + dropdownItem?: { + arrow?: ReactNode + } + formItem?: { + helpIcon?: ReactNode + } + imageUploader?: { + uploadIcon?: ReactNode + deleteIcon?: ReactNode + } + input?: { + clearIcon?: ReactNode + } + listItem?: { + arrow?: ReactNode + } + navBar?: { + backArrow?: ReactNode + } + noticeBar?: { + icon?: ReactNode + closeIcon?: ReactNode + } + numberKeyboard?: { + closeIcon?: ReactNode + deleteIcon?: ReactNode + } + popup?: { + closeIcon?: ReactNode + } + result?: { + successIcon?: ReactNode + errorIcon?: ReactNode + infoIcon?: ReactNode + waitingIcon?: ReactNode + warningIcon?: ReactNode + } + searchBar?: { + icon?: ReactNode + } + stepper?: { + plusIcon?: ReactNode + minusIcon?: ReactNode + } + toast?: { + successIcon?: ReactNode + failIcon?: ReactNode + loadingIcon?: ReactNode + } } export const defaultConfigRef: { @@ -23,9 +79,9 @@ export function getDefaultConfig() { return defaultConfigRef.current } -const ConfigContext = React.createContext(null) +const ConfigContext = React.createContext(defaultConfigRef.current) -export type ConfigProviderProps = Config & { +export type ConfigProviderProps = Partial & { children?: ReactNode } @@ -35,10 +91,7 @@ export const ConfigProvider: FC = props => { return ( {children} @@ -46,5 +99,5 @@ export const ConfigProvider: FC = props => { } export function useConfig() { - return useContext(ConfigContext) ?? getDefaultConfig() + return useContext(ConfigContext) } diff --git a/src/components/config-provider/index.en.md b/src/components/config-provider/index.en.md index c03304a3fb..5ecc931321 100644 --- a/src/components/config-provider/index.en.md +++ b/src/components/config-provider/index.en.md @@ -1,7 +1,72 @@ # ConfigProvider +Configure locale messages and custom icons globally. + +## When to use + +- You want to use other languages than English +- You want to use your own icon set instead of `antd-mobile-icons` + ## Demos + +## ConfigProvider + +### Props + +| Name | Description | Type | Default | +| --- | --- | --- | --- | +| locale | Locale messages | `Locale` | [base] | +| checkList | CheckList config | `{ activeIcon?: ReactNode }` | - | +| collapse | Collapse config | `{ arrow?: ReactNode \| ((active: boolean) => ReactNode) }` | - | +| dropdownItem | Dropdown.Item config | `{ arrow?: ReactNode }` | - | +| formItem | FormItem config | `{ helpIcon?: ReactNode }` | - | +| imageUploader | ImageUploader config | `{ uploadIcon?: ReactNode, deleteIcon?: ReactNode }` | - | +| input | Input config | `{ clearIcon?: ReactNode }` | - | +| listItem | List.Item config | `{ arrow?: ReactNode }` | - | +| navBar | NavBar config | `{ backArrow?: ReactNode }` | - | +| noticeBar | NoticeBar config | `{ icon?: ReactNode, closeIcon?: ReactNode }` | - | +| numberKeyboard | NumberKeyboard config | `{ closeIcon?: ReactNode, deleteIcon?: ReactNode }` | - | +| popup | Popup config | `{ closeIcon?: ReactNode }` | - | +| result | Result config | `{ successIcon?: ReactNode, errorIcon?: ReactNode, infoIcon?: ReactNode, waitingIcon?: ReactNode, warningIcon?: ReactNode }` | - | +| searchBar | SearchBar config | `{ icon?: ReactNode }` | - | +| stepper | Stepper config | `{ plusIcon?: ReactNode, minusIcon?: ReactNode }` | - | +| toast | Toast config | `{ successIcon?: ReactNode, failIcon?: ReactNode, loadingIcon?: ReactNode }` | - | + +## IconConfig + +Default icons are all from `antd-mobile-icons` package, except for SpinLoading which is a `antd-mobile` component. + +### Props + +| Name | Related components | Description | Default | +| --- | --- | --- | --- | +| CheckListActive | CheckList | Check mark | CheckOutline | +| CollapseArrow | Collapse | Expand/collapse | DownOutline | +| DropdownItemArrow | DropdownItem | Down arrow | DownFill | +| FormItemHelp | FormItem | Help indicator | QuestionCircleOutline | +| ImageUploaderUpload | ImageUploader | Upload button | AddOutline | +| ImageUploaderDelete | ImageUploader | Delete button | CloseOutline | +| InputClear | Input, SearchBar, VirtualInput | Clear button | CloseCircleFill | +| NavBarBackArrow | NavBar | Back button | LeftOutline | +| NoticeBarClose | NoticeBar | Close button | CloseOutline | +| NoticeBarNotice | NoticeBar | Notice | SoundOutline | +| NumberKeyboardClose | NumberKeyboard | Close/hide button | DownOutline | +| NumberKeyboardDelete | NumberKeyboard | Delete button | TextDeletionOutline | +| PopupClose | Popup, CenterPopup | Close button | CloseOutline | +| ResultSuccess | Result, ResultPage | Success status | CheckCircleFill | +| ResultError | Result, ResultPage | Error status | CloseCircleFill | +| ResultInfo | Result, ResultPage | Information status | InformationCircleFill | +| ResultWaiting | Result, ResultPage | Waiting status | ClockCircleFill | +| ResultWarning | Result, ResultPage | Warning status | ExclamationCircleFill | +| SearchBarSearch | SearchBar | Search | SearchOutline | +| StepperPlus | Stepper | Plus button | AddOutline | +| StepperMinus | Stepper | Minus button | MinusOutline | +| ToastSuccess | Toast | Success status | CheckOutline | +| ToastFail | Toast | Fail status | CloseOutline | +| ToastLoading | Toast | Loading status | SpinLoading | + +[base]: https://github.com/ant-design/ant-design-mobile/blob/master/src/locales/base.ts diff --git a/src/components/config-provider/index.zh.md b/src/components/config-provider/index.zh.md index 84560f3eea..548bfdbce3 100644 --- a/src/components/config-provider/index.zh.md +++ b/src/components/config-provider/index.zh.md @@ -1,7 +1,39 @@ # ConfigProvider 配置 +用于全局配置本地化文案和个性化图标。 + +## 何时使用 + +- 您想使用除英文之外的语言。 +- 您想使用自己的图标集,而不是内置的 `antd-mobile-icons`。 + ## 示例 + +## ConfigProvider + +### 属性 + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| locale | 本地化文案 | `Locale` | [base] | +| checkList | CheckList 配置 | `{ activeIcon?: ReactNode }` | - | +| collapse | Collapse 配置 | `{ arrow?: ReactNode \| ((active: boolean) => ReactNode) }` | - | +| dropdownItem | Dropdown.Item 配置 | `{ arrow?: ReactNode }` | - | +| formItem | FormItem 配置 | `{ helpIcon?: ReactNode }` | - | +| imageUploader | ImageUploader 配置 | `{ uploadIcon?: ReactNode, deleteIcon?: ReactNode }` | - | +| input | Input 配置 | `{ clearIcon?: ReactNode }` | - | +| listItem | List.Item 配置 | `{ arrow?: ReactNode }` | - | +| navBar | NavBar 配置 | `{ backArrow?: ReactNode }` | - | +| noticeBar | NoticeBar 配置 | `{ icon?: ReactNode, closeIcon?: ReactNode }` | - | +| numberKeyboard | NumberKeyboard 配置 | `{ closeIcon?: ReactNode, deleteIcon?: ReactNode }` | - | +| popup | Popup 配置 | `{ closeIcon?: ReactNode }` | - | +| result | Result 配置 | `{ successIcon?: ReactNode, errorIcon?: ReactNode, infoIcon?: ReactNode, waitingIcon?: ReactNode, warningIcon?: ReactNode }` | - | +| searchBar | SearchBar 配置 | `{ icon?: ReactNode }` | - | +| stepper | Stepper 配置 | `{ plusIcon?: ReactNode, minusIcon?: ReactNode }` | - | +| toast | Toast 配置 | `{ successIcon?: ReactNode, failIcon?: ReactNode, loadingIcon?: ReactNode }` | - | + +[base]: https://github.com/ant-design/ant-design-mobile/blob/master/src/locales/base.ts diff --git a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap index 7a0dd13fa0..f894f3d57e 100644 --- a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap +++ b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap @@ -4539,7 +4539,7 @@ exports[`ConfigProvider should display the text as da-DK 1`] = ` class="adm-image-uploader-cell-delete" > = props => { + const { dropdownItem: componentConfig = {} } = useConfig() + const { active, arrow, highlight, onClick, title } = mergeProps( + componentConfig, + props + ) const cls = classNames(classPrefix, { - [`${classPrefix}-active`]: props.active, - [`${classPrefix}-highlight`]: props.highlight ?? props.active, + [`${classPrefix}-active`]: active, + [`${classPrefix}-highlight`]: highlight ?? active, }) return withNativeProps( props, -
+
- {props.title} + {title} - {props.arrow === undefined ? : props.arrow} + {arrow === undefined ? : arrow}
diff --git a/src/components/form/form-item.tsx b/src/components/form/form-item.tsx index 8a21f75027..cb7036c458 100644 --- a/src/components/form/form-item.tsx +++ b/src/components/form/form-item.tsx @@ -15,6 +15,7 @@ import Popover from '../popover' import { QuestionCircleOutline } from 'antd-mobile-icons' import { useConfig } from '../config-provider' import { undefinedFallback } from '../../utils/undefined-fallback' +import { mergeProps } from '../../utils/with-default-props' const NAME_SPLIT = '__SPLIT__' @@ -48,6 +49,7 @@ export type FormItemProps = Pick< > & { label?: ReactNode help?: ReactNode + helpIcon?: ReactNode hasFeedback?: boolean required?: boolean noStyle?: boolean @@ -79,6 +81,7 @@ type FormItemLayoutProps = Pick< | 'disabled' | 'label' | 'help' + | 'helpIcon' | 'hidden' | 'layout' | 'extra' @@ -95,23 +98,24 @@ type FormItemLayoutProps = Pick< } & NativeProps const FormItemLayout: FC = props => { + const { locale, formItem: componentConfig = {} } = useConfig() + const { style, extra, label, help, + helpIcon, required, children, htmlFor, hidden, arrow, childElementPosition = 'normal', - } = props + } = mergeProps(componentConfig, props) const context = useContext(FormContext) - const { locale } = useConfig() - const hasFeedback = props.hasFeedback !== undefined ? props.hasFeedback : context.hasFeedback const layout = props.layout || context.layout @@ -162,7 +166,7 @@ const FormItemLayout: FC = props => { e.preventDefault() }} > - + {helpIcon || } )} diff --git a/src/components/form/index.en.md b/src/components/form/index.en.md index 8c3de36a81..f540a580e6 100644 --- a/src/components/form/index.en.md +++ b/src/components/form/index.en.md @@ -93,6 +93,7 @@ const validateMessages = { | getValueProps | Customize additional props with value. This prop will disable `valuePropName` | `(value) => any` | - | | hasFeedback | Whether to show error feedback | `boolean` | `true` | | help | Prompt text | `ReactNode` | - | +| helpIcon | Prompt icon | `ReactNode` | `` | | hidden | Hide this field | `boolean` | `false` | | initialValue | Config sub default value. Form `initialValues` get higher priority when conflict. | `any` | - | | label | Label name | `ReactNode` | - | diff --git a/src/components/form/index.zh.md b/src/components/form/index.zh.md index c937dadfc1..2bc3bcf009 100644 --- a/src/components/form/index.zh.md +++ b/src/components/form/index.zh.md @@ -93,6 +93,7 @@ const validateMessages = { | getValueProps | 为子元素添加额外的属性 | `(value) => any` | - | | hasFeedback | 是否展示错误反馈 | `boolean` | `true` | | help | 提示文本 | `ReactNode` | - | +| helpIcon | 提示图标 | `ReactNode` | `` | | hidden | 是否隐藏整个字段 | `boolean` | `false` | | initialValue | 设置子元素默认值,如果与 Form 的 `initialValues` 冲突则以 Form 为准 | `any` | - | | label | 标签名 | `ReactNode` | - | diff --git a/src/components/image-uploader/image-uploader.less b/src/components/image-uploader/image-uploader.less index dfaba83e8d..b465c91408 100644 --- a/src/components/image-uploader/image-uploader.less +++ b/src/components/image-uploader/image-uploader.less @@ -37,7 +37,7 @@ font-size: 8px; color: var(--adm-color-white); cursor: pointer; - &-icon { + svg { position: absolute; left: 4px; top: 3px; diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 699218b142..724399e303 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -50,6 +50,7 @@ export type ImageUploaderProps = { showUpload?: boolean deletable?: boolean deleteIcon?: ReactNode + onDelete?: (item: ImageUploadItem) => boolean | Promise | void capture?: InputHTMLAttributes['capture'] onPreview?: (index: number, item: ImageUploadItem) => void beforeUpload?: ( @@ -57,7 +58,7 @@ export type ImageUploaderProps = { files: File[] ) => Promise | File | null upload: (file: File) => Promise - onDelete?: (item: ImageUploadItem) => boolean | Promise | void + uploadIcon?: ReactNode preview?: boolean showFailed?: boolean imageFit?: ImageProps['fit'] @@ -78,7 +79,6 @@ const classPrefix = `adm-image-uploader` const defaultProps = { disableUpload: false, deletable: true, - deleteIcon: , showUpload: true, multiple: false, maxCount: 0, @@ -87,12 +87,15 @@ const defaultProps = { preview: true, showFailed: true, imageFit: 'cover', + deleteIcon: , + uploadIcon: , } export const ImageUploader = forwardRef( (p, ref) => { - const { locale } = useConfig() - const props = mergeProps(defaultProps, p) + const { locale, imageUploader: componentConfig = {} } = useConfig() + + const props = mergeProps(defaultProps, componentConfig, p) const { columns } = props const [value, setValue] = usePropsValue(props) @@ -312,7 +315,7 @@ export const ImageUploader = forwardRef( aria-label={locale.ImageUploader.upload} > - + {props.uploadIcon} )} diff --git a/src/components/image-uploader/index.en.md b/src/components/image-uploader/index.en.md index 98e5cde113..de58666587 100644 --- a/src/components/image-uploader/index.en.md +++ b/src/components/image-uploader/index.en.md @@ -20,6 +20,7 @@ | defaultValue | Default list of uploaded files | `ImageUploadItem[]` | - | | deletable | Whether to show the delete button | `boolean` | `true` | | deleteIcon | The customized `icon` in delete button | `ReactNode` | `` | +| uploadIcon | The customized `icon` in upload button | `ReactNode` | `` | | disableUpload | Whether to disable the upload button | `boolean` | `false` | | imageFit | The fill mode of the image, same as [Image](/components/image#props) | `'contain' \| 'cover' \| 'fill' \| 'none' \| 'scale-down'` | `cover` | | maxCount | To control how many pictures to be uploaded at most, the upload button would be automatically hidden if the number is exceeded, `0` means no limit | `number` | `0` | diff --git a/src/components/image-uploader/index.zh.md b/src/components/image-uploader/index.zh.md index 5e15cb87dc..fd42681fb1 100644 --- a/src/components/image-uploader/index.zh.md +++ b/src/components/image-uploader/index.zh.md @@ -20,6 +20,7 @@ | defaultValue | 默认已上传的文件列表 | `ImageUploadItem[]` | - | | deletable | 是否展示删除按钮 | `boolean` | `true` | | deleteIcon | 删除按钮的 `icon` 图标 | `ReactNode` | `` | +| uploadIcon | 上传按钮的 `icon` 图标 | `ReactNode` | `` | | disableUpload | 是否禁用上传按钮 | `boolean` | `false` | | imageFit | 图片填充模式,同 [Image](/zh/components/image#属性) | `'contain' \| 'cover' \| 'fill' \| 'none' \| 'scale-down'` | `cover` | | maxCount | 最多上传几张图片,超出数量会自动隐藏上传按钮,`0` 表示不做限制 | `number` | `0` | diff --git a/src/components/image-uploader/tests/__snapshots__/image-uploader.test.tsx.snap b/src/components/image-uploader/tests/__snapshots__/image-uploader.test.tsx.snap index f52534bbe5..5d816240b9 100644 --- a/src/components/image-uploader/tests/__snapshots__/image-uploader.test.tsx.snap +++ b/src/components/image-uploader/tests/__snapshots__/image-uploader.test.tsx.snap @@ -42,7 +42,7 @@ exports[`ImageUploader \`preview\` & \`onPreview\` prop 1`] = ` class="adm-image-uploader-cell-delete" > ` | | defaultValue | The default value | `string` | - | | disabled | Whether it is disabled or not | `boolean` | `false` | | id | The `id` of the `input` element, usually used with `label` | `string` | - | diff --git a/src/components/input/index.zh.md b/src/components/input/index.zh.md index 72d69b76d1..4f3f937813 100644 --- a/src/components/input/index.zh.md +++ b/src/components/input/index.zh.md @@ -21,6 +21,7 @@ | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | clearable | 是否启用清除图标,点击清除图标后会清空输入框 | `boolean` | `false` | +| clearIcon | 自定义清除图标 | `ReactNode` | `` | | defaultValue | 默认值 | `string` | - | | disabled | 是否禁用 | `boolean` | `false` | | id | `input` 元素的 `id`,常用来配合 `label` 使用 | `string` | - | diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx index 94d8dfdc78..e61033c825 100644 --- a/src/components/input/input.tsx +++ b/src/components/input/input.tsx @@ -1,4 +1,10 @@ -import React, { useState, forwardRef, useImperativeHandle, useRef } from 'react' +import React, { + useState, + forwardRef, + useImperativeHandle, + useRef, + ReactNode, +} from 'react' import { usePropsValue } from '../../utils/use-props-value' import { CloseCircleFill } from 'antd-mobile-icons' import { NativeProps, withNativeProps } from '../../utils/native-props' @@ -50,6 +56,7 @@ export type InputProps = Pick< defaultValue?: string onChange?: (val: string) => void clearable?: boolean + clearIcon?: ReactNode onlyShowClearWhenFocus?: boolean onClear?: () => void onEnterPress?: (e: React.KeyboardEvent) => void @@ -70,6 +77,7 @@ export type InputProps = Pick< const defaultProps = { defaultValue: '', + clearIcon: , onlyShowClearWhenFocus: true, } @@ -81,12 +89,13 @@ export type InputRef = { } export const Input = forwardRef((p, ref) => { - const props = mergeProps(defaultProps, p) + const { locale, input: componentConfig = {} } = useConfig() + + const props = mergeProps(defaultProps, componentConfig, p) const [value, setValue] = usePropsValue(props) const [hasFocus, setHasFocus] = useState(false) const compositionStartRef = useRef(false) const nativeInputRef = useRef(null) - const { locale } = useConfig() useImperativeHandle(ref, () => ({ clear: () => { @@ -219,7 +228,7 @@ export const Input = forwardRef((p, ref) => { }} aria-label={locale.Input.clear} > - + {props.clearIcon}
)} diff --git a/src/components/list/list-item.tsx b/src/components/list/list-item.tsx index d005c59ee1..a37b2172cc 100644 --- a/src/components/list/list-item.tsx +++ b/src/components/list/list-item.tsx @@ -4,6 +4,7 @@ import { NativeProps, withNativeProps } from '../../utils/native-props' import { RightOutline } from 'antd-mobile-icons' import classNames from 'classnames' import { isNodeWithContent } from '../../utils/is-node-with-content' +import { useConfig } from '../config-provider' const classPrefix = `adm-list-item` @@ -22,6 +23,7 @@ export type ListItemProps = { > export const ListItem: FC = props => { + const { listItem: componentConfig = {} } = useConfig() const clickable = props.clickable ?? !!props.onClick const arrow = props.arrow === undefined ? clickable : props.arrow @@ -46,7 +48,7 @@ export const ListItem: FC = props => { )} {isNodeWithContent(arrow) && (
- {arrow === true ? : arrow} + {arrow === true ? componentConfig?.arrow || : arrow}
)} diff --git a/src/components/nav-bar/nav-bar.tsx b/src/components/nav-bar/nav-bar.tsx index 81ff6ef9c9..db784c8513 100644 --- a/src/components/nav-bar/nav-bar.tsx +++ b/src/components/nav-bar/nav-bar.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames' import { LeftOutline } from 'antd-mobile-icons' import { NativeProps, withNativeProps } from '../../utils/native-props' import { mergeProps } from '../../utils/with-default-props' +import { useConfig } from '../config-provider' const classPrefix = `adm-nav-bar` @@ -19,8 +20,10 @@ export type NavBarProps = { const defaultProps = { backArrow: true, } + export const NavBar: FC = p => { - const props = mergeProps(defaultProps, p) + const { navBar: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const { back, backArrow } = props return withNativeProps( diff --git a/src/components/nav-bar/tests/nav-bar.test.tsx b/src/components/nav-bar/tests/nav-bar.test.tsx new file mode 100644 index 0000000000..81e34ab647 --- /dev/null +++ b/src/components/nav-bar/tests/nav-bar.test.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { render, screen } from 'testing' +import NavBar from '..' + +const classPrefix = `adm-result` + +describe('NavBar', () => { + test('render title', () => { + render(Title) + expect(screen.getByText('Title')).toBeInTheDocument() + }) + test('render back arrow', () => { + render(Title) + expect(screen.getByText('Title')).toBeInTheDocument() + }) + test('render custom back arrow', () => { + render(Title) + expect(screen.getByText('Back')).toBeInTheDocument() + }) +}) diff --git a/src/components/notice-bar/index.en.md b/src/components/notice-bar/index.en.md index ab0c30fa87..4149d54876 100644 --- a/src/components/notice-bar/index.en.md +++ b/src/components/notice-bar/index.en.md @@ -17,6 +17,7 @@ It is applicable to the notification of information in the current page, which i | Name | Description | Type | Default | | --- | --- | --- | --- | | closeable | Whether it can be closed | `boolean` | `false` | +| closeIcon | Custom close button icon | `ReactNode` | `` | | color | The type of the NoticeBar | `'default' \| 'alert' \| 'error' \| 'info'` | `'default'` | | content | The content of the NoticeBar | `React.ReactNode` | - | | delay | Delay to start scrolling, unit `ms` | `number` | `2000` | diff --git a/src/components/notice-bar/index.zh.md b/src/components/notice-bar/index.zh.md index 65f5d0a240..14d5407e48 100644 --- a/src/components/notice-bar/index.zh.md +++ b/src/components/notice-bar/index.zh.md @@ -17,6 +17,7 @@ | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | closeable | 是否可关闭 | `boolean` | `false` | +| closeIcon | 自定义关闭按钮图标 | `ReactNode` | `` | | color | 通告栏的类型 | `'default' \| 'alert' \| 'error' \| 'info'` | `'default'` | | content | 公告内容 | `React.ReactNode` | - | | delay | 开始滚动的延迟,单位 `ms` | `number` | `2000` | diff --git a/src/components/notice-bar/notice-bar.less b/src/components/notice-bar/notice-bar.less index d23d833844..13ef66513b 100644 --- a/src/components/notice-bar/notice-bar.less +++ b/src/components/notice-bar/notice-bar.less @@ -79,9 +79,6 @@ display: flex; align-items: center; justify-content: center; - } - - &-close-icon { font-size: var(--adm-font-size-10); } diff --git a/src/components/notice-bar/notice-bar.tsx b/src/components/notice-bar/notice-bar.tsx index 655cb462cd..d401d4d6d0 100644 --- a/src/components/notice-bar/notice-bar.tsx +++ b/src/components/notice-bar/notice-bar.tsx @@ -7,6 +7,7 @@ import { mergeProps } from '../../utils/with-default-props' import { NativeProps, withNativeProps } from '../../utils/native-props' import { useResizeEffect } from '../../utils/use-resize-effect' import { useMutationEffect } from '../../utils/use-mutation-effect' +import { useConfig } from '../config-provider' const classPrefix = `adm-notice-bar` @@ -44,12 +45,14 @@ const defaultProps = { color: 'default', delay: 2000, speed: 50, - wrap: false, icon: , + closeIcon: , + wrap: false, } export const NoticeBar = memo(p => { - const props = mergeProps(defaultProps, p) + const { noticeBar: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const containerRef = useRef(null) const textRef = useRef(null) @@ -149,7 +152,7 @@ export const NoticeBar = memo(p => { props.onClose?.() }} > - + {props.closeIcon} )} diff --git a/src/components/notice-bar/tests/notice-bar.test.tsx b/src/components/notice-bar/tests/notice-bar.test.tsx index 66490b704a..e0c1e542e6 100644 --- a/src/components/notice-bar/tests/notice-bar.test.tsx +++ b/src/components/notice-bar/tests/notice-bar.test.tsx @@ -30,7 +30,7 @@ describe('NoticeBar', () => { ) const el = getByTestId('notice') - const iconEl = el.querySelectorAll(`.${classPrefix}-close-icon`)[0] + const iconEl = el.querySelectorAll(`.${classPrefix}-close`)[0] expect(iconEl).toBeVisible() fireEvent.click(iconEl) diff --git a/src/components/number-keyboard/index.en.md b/src/components/number-keyboard/index.en.md index aacb49ac49..21d966882f 100644 --- a/src/components/number-keyboard/index.en.md +++ b/src/components/number-keyboard/index.en.md @@ -33,8 +33,10 @@ tips: It is recommended to open the demo on the mobile side for better preview e | 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` | | onClose | Triggered when it is clicked | `() => void` | - | +| closeIcon | Custom close button icon | `ReactNode` | `` | | onConfirm | Triggered when the ok button is clicked | `() => void` | - | | onDelete | Callback when the content is deleted | `() => void` | - | +| deleteIcon | Custom delete button icon | `ReactNode` | `` | | onInput | Callback when the input content is changed | `(v: string) => void` | - | | randomOrder | Whether the keyboard is out of order | `boolean` | `false` | | safeArea | Whether to enable safe area padding | `boolean` | `true` | diff --git a/src/components/number-keyboard/index.zh.md b/src/components/number-keyboard/index.zh.md index f59a9ffe26..7a10dcd688 100644 --- a/src/components/number-keyboard/index.zh.md +++ b/src/components/number-keyboard/index.zh.md @@ -33,8 +33,10 @@ tips: 建议在移动端打开 demo 预览效果更佳,因为组件使用的 t | forceRender | 强制渲染内容 | `boolean` | `false` | | getContainer | 指定挂载的 HTML 节点,默认为 `body`,如果为 `null` 的话,会渲染到当前节点 | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` | | onClose | 点击关闭时触发 | `() => void` | - | +| closeIcon | 自定义关闭图标 | `ReactNode` | `` | | onConfirm | 点击确定按钮时触发 | `() => void` | - | | onDelete | 删除内容回调 | `() => void` | - | +| deleteIcon | 自定义删除图标 | `ReactNode` | `` | | onInput | 输入内容回调 | `(v: string) => void` | - | | randomOrder | 是否乱序键盘 | `boolean` | `false` | | safeArea | 是否开启安全区适配 | `boolean` | `true` | diff --git a/src/components/number-keyboard/number-keyboard.tsx b/src/components/number-keyboard/number-keyboard.tsx index ab4ecac484..1132a9ba30 100644 --- a/src/components/number-keyboard/number-keyboard.tsx +++ b/src/components/number-keyboard/number-keyboard.tsx @@ -1,5 +1,5 @@ import React, { useRef, useMemo } from 'react' -import type { FC, TouchEvent, MouseEvent } from 'react' +import type { FC, TouchEvent, MouseEvent, ReactNode } from 'react' import classNames from 'classnames' import { DownOutline, TextDeletionOutline } from 'antd-mobile-icons' import { mergeProps } from '../../utils/with-default-props' @@ -21,7 +21,9 @@ export type NumberKeyboardProps = { showCloseButton?: boolean onInput?: (v: string) => void onDelete?: () => void + deleteIcon?: ReactNode onClose?: () => void + closeIcon?: ReactNode onConfirm?: () => void closeOnConfirm?: boolean safeArea?: boolean @@ -42,13 +44,16 @@ const defaultProps = { showCloseButton: true, confirmText: null, closeOnConfirm: true, + closeIcon: , + deleteIcon: , safeArea: true, destroyOnClose: false, forceRender: false, } export const NumberKeyboard: FC = p => { - const props = mergeProps(defaultProps, p) + const { locale, numberKeyboard: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const { visible, title, @@ -58,10 +63,10 @@ export const NumberKeyboard: FC = p => { randomOrder, showCloseButton, onInput, + closeIcon, + deleteIcon, } = props - const { locale } = useConfig() - const keyboardRef = useRef(null) const keys = useMemo(() => { @@ -145,7 +150,7 @@ export const NumberKeyboard: FC = p => { title={locale.common.close} tabIndex={-1} > - + {closeIcon} )} @@ -186,7 +191,7 @@ export const NumberKeyboard: FC = p => { }} {...ariaProps} > - {key === 'BACKSPACE' ? : key} + {key === 'BACKSPACE' ? deleteIcon : key} ) } @@ -240,7 +245,7 @@ export const NumberKeyboard: FC = p => { role='button' tabIndex={-1} > - + {deleteIcon}
` | | destroyOnClose | Destroy `dom` 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/popup/index.zh.md b/src/components/popup/index.zh.md index 6c699b774c..88f9f7ad56 100644 --- a/src/components/popup/index.zh.md +++ b/src/components/popup/index.zh.md @@ -24,6 +24,7 @@ | bodyStyle | 内容区域样式 | `React.CSSProperties` | - | | className | 容器类名 | `string` | - | | closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `false` | +| closeIcon | 自定义关闭按钮图标 | `ReactNode` | `` | | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` | | forceRender | 强制渲染内容 | `boolean` | `false` | | getContainer | 指定挂载的 `HTML` 节点,默认为 `body`,如果为 `null` 的话,会渲染到当前节点 | `HTMLElement \| () => HTMLElement \| null` | `() => document.body` | diff --git a/src/components/popup/popup-base-props.ts b/src/components/popup/popup-base-props.tsx similarity index 87% rename from src/components/popup/popup-base-props.ts rename to src/components/popup/popup-base-props.tsx index dc3817366a..ff43fd7347 100644 --- a/src/components/popup/popup-base-props.ts +++ b/src/components/popup/popup-base-props.tsx @@ -1,8 +1,9 @@ import React from 'react' -import type { CSSProperties } from 'react' +import type { CSSProperties, ReactNode } from 'react' import { GetContainer } from '../../utils/render-to-container' import { MaskProps } from '../mask' import { PropagationEvent } from '../../utils/with-stop-propagation' +import { CloseOutline } from 'antd-mobile-icons' export type PopupBaseProps = { afterClose?: () => void @@ -10,6 +11,7 @@ export type PopupBaseProps = { bodyClassName?: string bodyStyle?: CSSProperties closeOnMaskClick?: boolean + closeIcon?: ReactNode destroyOnClose?: boolean disableBodyScroll?: boolean forceRender?: boolean @@ -27,6 +29,7 @@ export type PopupBaseProps = { export const defaultPopupBaseProps = { closeOnMaskClick: false, + closeIcon: , destroyOnClose: false, disableBodyScroll: true, forceRender: false, diff --git a/src/components/popup/popup.tsx b/src/components/popup/popup.tsx index 45bb0570e9..c736cb0b08 100644 --- a/src/components/popup/popup.tsx +++ b/src/components/popup/popup.tsx @@ -10,7 +10,6 @@ import { renderToContainer } from '../../utils/render-to-container' import { useSpring, animated } from '@react-spring/web' import { withStopPropagation } from '../../utils/with-stop-propagation' import { ShouldRender } from '../../utils/should-render' -import { CloseOutline } from 'antd-mobile-icons' import { defaultPopupBaseProps, PopupBaseProps } from './popup-base-props' import { useInnerVisible } from '../../utils/use-inner-visible' import { useConfig } from '../config-provider' @@ -32,7 +31,8 @@ const defaultProps = { } export const Popup: FC = p => { - const props = mergeProps(defaultProps, p) + const { locale, popup: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const bodyCls = classNames( `${classPrefix}-body`, @@ -40,7 +40,6 @@ export const Popup: FC = p => { `${classPrefix}-body-position-${props.position}` ) - const { locale } = useConfig() const [active, setActive] = useState(props.visible) const ref = useRef(null) useLockScroll(ref, props.disableBodyScroll && active ? 'strict' : false) @@ -156,7 +155,7 @@ export const Popup: FC = p => { role='button' aria-label={locale.common.close} > - + {props.closeIcon} )} {props.children} diff --git a/src/components/result-page/result-page.tsx b/src/components/result-page/result-page.tsx index d896da6c65..e499f82970 100644 --- a/src/components/result-page/result-page.tsx +++ b/src/components/result-page/result-page.tsx @@ -1,29 +1,14 @@ import React, { useState } from 'react' import type { FC, ReactNode } from 'react' - -import { - CheckCircleFill, - CloseCircleFill, - InformationCircleFill, - ClockCircleFill, - ExclamationCircleFill, -} from 'antd-mobile-icons' import classNames from 'classnames' import { NativeProps, withNativeProps } from '../../utils/native-props' import { mergeProps } from '../../utils/with-default-props' import { isNodeWithContent } from '../../utils/is-node-with-content' import Button from '../button' +import { ResultIcon } from '../result/result-icon' const classPrefix = `adm-result-page` -const iconRecord = { - success: CheckCircleFill, - error: CloseCircleFill, - info: InformationCircleFill, - waiting: ClockCircleFill, - warning: ExclamationCircleFill, -} - interface ResultPageDetail { label: ReactNode value: ReactNode @@ -67,7 +52,6 @@ export const ResultPage: FC = p => { onPrimaryButtonClick, onSecondaryButtonClick, } = props - const resultIcon = icon || React.createElement(iconRecord[status]) const [collapse, setCollapse] = useState(true) @@ -78,7 +62,9 @@ export const ResultPage: FC = p => { props,
-
{resultIcon}
+
+ {icon || } +
{title}
{isNodeWithContent(description) ? (
{description}
diff --git a/src/components/result/result-icon.tsx b/src/components/result/result-icon.tsx new file mode 100644 index 0000000000..44fb5d15f0 --- /dev/null +++ b/src/components/result/result-icon.tsx @@ -0,0 +1,38 @@ +import React, { FC, ReactElement } from 'react' +import { + CheckCircleFill, + CloseCircleFill, + InformationCircleFill, + ClockCircleFill, + ExclamationCircleFill, +} from 'antd-mobile-icons' +import { useConfig } from '../config-provider' + +export type ResultIconProps = { + status?: 'success' | 'error' | 'info' | 'waiting' | 'warning' +} + +export const ResultIcon: FC = ({ status }) => { + const { result: componentConfig = {} } = useConfig() + const { + successIcon = , + errorIcon = , + infoIcon = , + waitingIcon = , + warningIcon = , + } = componentConfig || {} + switch (status) { + case 'success': + return <>{successIcon} + case 'error': + return <>{errorIcon} + case 'info': + return <>{infoIcon} + case 'waiting': + return <>{waitingIcon} + case 'warning': + return <>{warningIcon} + default: + return null + } +} diff --git a/src/components/result/result.tsx b/src/components/result/result.tsx index 32041b9bef..9dad3b421a 100644 --- a/src/components/result/result.tsx +++ b/src/components/result/result.tsx @@ -1,26 +1,12 @@ import React from 'react' import type { FC, ReactNode } from 'react' import classNames from 'classnames' -import { - CheckCircleFill, - CloseCircleFill, - InformationCircleFill, - ClockCircleFill, - ExclamationCircleFill, -} from 'antd-mobile-icons' import { NativeProps, withNativeProps } from '../../utils/native-props' import { mergeProps } from '../../utils/with-default-props' +import { ResultIcon } from './result-icon' const classPrefix = `adm-result` -const iconRecord = { - success: CheckCircleFill, - error: CloseCircleFill, - info: InformationCircleFill, - waiting: ClockCircleFill, - warning: ExclamationCircleFill, -} - const defaultProps = { status: 'info', } @@ -36,12 +22,13 @@ export const Result: FC = p => { const props = mergeProps(defaultProps, p) const { status, title, description, icon } = props if (!status) return null - const resultIcon = icon || React.createElement(iconRecord[status]) return withNativeProps( props,
-
{resultIcon}
+
+ {icon || } +
{title}
{!!description && (
{description}
diff --git a/src/components/result/tests/result-icon.test.tsx b/src/components/result/tests/result-icon.test.tsx new file mode 100644 index 0000000000..82562e78a1 --- /dev/null +++ b/src/components/result/tests/result-icon.test.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { render, screen } from 'testing' +import Result from '..' +import { ResultIcon } from '../result-icon' + +const classPrefix = `adm-result` + +describe('ResultIcon', () => { + test('renders with success status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeTruthy() + }) + test('renders with error status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeTruthy() + }) + test('renders with info status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeTruthy() + }) + test('renders with waiting status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeTruthy() + }) + test('renders with warning status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeTruthy() + }) + test('renders with none status', () => { + render() + expect(document.querySelector('.antd-mobile-icon')).toBeFalsy() + }) +}) diff --git a/src/components/search-bar/search-bar.tsx b/src/components/search-bar/search-bar.tsx index 0614f7207f..0ad89cce83 100644 --- a/src/components/search-bar/search-bar.tsx +++ b/src/components/search-bar/search-bar.tsx @@ -48,9 +48,10 @@ const defaultProps = { } export const SearchBar = forwardRef((p, ref) => { - const { locale } = useConfig() + const { locale, searchBar: componentConfig = {} } = useConfig() const props = mergeProps( defaultProps, + componentConfig, { cancelText: locale.common.cancel, }, diff --git a/src/components/stepper/index.en.md b/src/components/stepper/index.en.md index 51dd23898e..b7cec89b13 100644 --- a/src/components/stepper/index.en.md +++ b/src/components/stepper/index.en.md @@ -28,6 +28,8 @@ It is suitable for inputting and adjusting the current value within a certain ra | inputReadOnly | Whether input readonly or not | `boolean` | `false` | | max | Max value | `number` | - | | min | Min value | `number` | - | +| plusIcon | Custom plus button icon | `ReactNode` | `` | +| minusIcon | Custom minus button icon | `ReactNode` | `` | | onBlur | Triggered when the input lose focus | `(e: React.FocusEvent) => void` | - | | onChange | Callback when value is changed | `(value: number \| null) => void` | - | | onFocus | Triggered when the input get focus | `(e: React.FocusEvent) => void` | - | diff --git a/src/components/stepper/index.zh.md b/src/components/stepper/index.zh.md index 77832e9a80..bba1e4e2fe 100644 --- a/src/components/stepper/index.zh.md +++ b/src/components/stepper/index.zh.md @@ -28,6 +28,8 @@ | inputReadOnly | 输入框是否只读 | `boolean` | `false` | | max | 最大值 | `number` | - | | min | 最小值 | `number` | - | +| plusIcon | 自定义加号图标 | `ReactNode` | `` | +| minusIcon | 自定义减号图标 | `ReactNode` | `` | | onBlur | 输入框失去焦点时触发 | `(e: React.FocusEvent) => void` | - | | onChange | 变化时的回调 | `(value: number \| null) => void` | - | | onFocus | 输入框获得焦点时触发 | `(e: React.FocusEvent) => void` | - | diff --git a/src/components/stepper/stepper.tsx b/src/components/stepper/stepper.tsx index 8c1b0f1a03..9198f2cbc6 100644 --- a/src/components/stepper/stepper.tsx +++ b/src/components/stepper/stepper.tsx @@ -4,6 +4,7 @@ import React, { useState, forwardRef, useImperativeHandle, + ReactNode, } from 'react' import { MinusOutline, AddOutline } from 'antd-mobile-icons' import { useMergedState } from 'rc-util' @@ -44,6 +45,8 @@ export type BaseStepperProps = Pick< digits?: number disabled?: boolean inputReadOnly?: boolean + plusIcon?: ReactNode + minusIcon?: ReactNode // Format & Parse parser?: (text: string) => ValueType @@ -90,13 +93,16 @@ const defaultProps = { step: 1, disabled: false, allowEmpty: false, + plusIcon: , + minusIcon: , } export function InnerStepper( p: StepperProps, ref: React.ForwardedRef ) { - const props = mergeProps(defaultProps, p) + const { locale, stepper: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const { defaultValue = 0 as ValueType, value, @@ -112,8 +118,6 @@ export function InnerStepper( parser, } = props as MergedStepperProps - const { locale } = useConfig() - // ========================== Ref ========================== useImperativeHandle(ref, () => ({ focus: () => { @@ -298,7 +302,7 @@ export function InnerStepper( color='primary' aria-label={locale.Stepper.decrease} > - + {props.minusIcon}
( color='primary' aria-label={locale.Stepper.increase} > - + {props.plusIcon}
) diff --git a/src/components/toast/tests/toast.test.tsx b/src/components/toast/tests/toast.test.tsx index 44cacfc5f5..0d17950bb6 100644 --- a/src/components/toast/tests/toast.test.tsx +++ b/src/components/toast/tests/toast.test.tsx @@ -60,20 +60,18 @@ describe('Toast', () => { fireEvent.click(getByText('success')) await waitForContentShow('content success') expect( - document.querySelectorAll(`.${classPrefix}-icon-success`)[0] + document.getElementById('CheckOutline-CheckOutline') ).toBeInTheDocument() fireEvent.click(getByText('fail')) await waitForContentShow('content fail') expect( - document.querySelectorAll(`.${classPrefix}-icon-fail`)[0] + document.getElementById('CloseOutline-CloseOutline') ).toBeInTheDocument() fireEvent.click(getByText('loading')) await waitForContentShow('content loading') - expect( - document.querySelectorAll(`.${classPrefix}-loading`)[0] - ).toBeInTheDocument() + expect(document.querySelector('.adm-spin-loading')).toBeInTheDocument() }) test('custom icon', async () => { diff --git a/src/components/toast/toast.less b/src/components/toast/toast.less index 7a23a018a7..d3d855f2ac 100644 --- a/src/components/toast/toast.less +++ b/src/components/toast/toast.less @@ -43,8 +43,3 @@ } } } - -.@{class-prefix-toast}-loading { - --size: 48px; - margin: 0 auto 8px; -} diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx index b9e23ac7a9..b668101eca 100644 --- a/src/components/toast/toast.tsx +++ b/src/components/toast/toast.tsx @@ -9,6 +9,7 @@ import { PropagationEvent } from '../../utils/with-stop-propagation' import { GetContainer } from '../../utils/render-to-container' import AutoCenter from '../auto-center' import SpinLoading from '../spin-loading' +import { useConfig } from '../config-provider' const classPrefix = `adm-toast` @@ -32,6 +33,7 @@ const defaultProps = { } export const InternalToast: FC = p => { + const { toast: componentConfig = {} } = useConfig() const props = mergeProps(defaultProps, p) const { maskClickable, content, icon, position } = props @@ -39,12 +41,14 @@ export const InternalToast: FC = p => { if (icon === null || icon === undefined) return null switch (icon) { case 'success': - return + return componentConfig?.successIcon || case 'fail': - return + return componentConfig?.failIcon || case 'loading': return ( - + componentConfig?.loadingIcon || ( + + ) ) default: return icon diff --git a/src/components/virtual-input/virtual-input.tsx b/src/components/virtual-input/virtual-input.tsx index 955496f4b0..bbdbacd183 100644 --- a/src/components/virtual-input/virtual-input.tsx +++ b/src/components/virtual-input/virtual-input.tsx @@ -25,7 +25,10 @@ export type VirtualInputProps = { keyboard?: ReactElement clearable?: boolean onClear?: () => void -} & Pick & +} & Pick< + InputProps, + 'value' | 'onChange' | 'placeholder' | 'disabled' | 'clearIcon' +> & NativeProps< | '--font-size' | '--color' @@ -38,6 +41,7 @@ export type VirtualInputProps = { const defaultProps = { defaultValue: '', + clearIcon: , } export type VirtualInputRef = { @@ -47,12 +51,12 @@ export type VirtualInputRef = { export const VirtualInput = forwardRef( (p, ref) => { - const props = mergeProps(defaultProps, p) + const { locale, input: componentConfig = {} } = useConfig() + const props = mergeProps(defaultProps, componentConfig, p) const [value, setValue] = usePropsValue(props) const rootRef = useRef(null) const contentRef = useRef(null) const [hasFocus, setHasFocus] = useState(false) - const { locale } = useConfig() function scrollToEnd() { const root = rootRef.current @@ -157,7 +161,7 @@ export const VirtualInput = forwardRef( role='button' aria-label={locale.Input.clear} > - + {props.clearIcon}
)} {[undefined, null, ''].includes(value) && ( diff --git a/src/utils/with-default-props.tsx b/src/utils/with-default-props.tsx index 3446d7dea6..84bbff841f 100644 --- a/src/utils/with-default-props.tsx +++ b/src/utils/with-default-props.tsx @@ -1,5 +1,6 @@ export function mergeProps(a: A, b: B): B & A export function mergeProps(a: A, b: B, c: C): C & B & A +export function mergeProps(a: A, b: B, c: C, d: D): D & C & B & A export function mergeProps(...items: any[]) { const ret: any = {} items.forEach(item => {