From 76104a8358ec51ed8992c8ffeaa5e42d25fd26dd Mon Sep 17 00:00:00 2001 From: Guo Yunhe Date: Wed, 3 Apr 2024 22:06:46 +0800 Subject: [PATCH] feat(ConfigProvider): add icons config --- src/components/center-popup/center-popup.tsx | 6 ++- src/components/check-list/check-list.tsx | 9 ++-- src/components/collapse/collapse.tsx | 6 ++- .../config-provider/config-provider.tsx | 50 ++++++++++++++++--- .../tests/config-provider.test.tsx | 4 +- src/components/dropdown/item.tsx | 8 ++- src/components/form/form-item.tsx | 7 ++- .../image-uploader/image-uploader.tsx | 22 ++++++-- src/components/input/input.tsx | 7 ++- src/components/list/list-item.tsx | 6 ++- src/components/nav-bar/nav-bar.tsx | 6 ++- src/components/notice-bar/notice-bar.tsx | 9 ++-- .../number-keyboard/number-keyboard.tsx | 14 ++++-- src/components/popup/popup.tsx | 7 ++- src/components/result-page/result-page.tsx | 22 ++------ src/components/result/result-icon.tsx | 39 +++++++++++++++ src/components/result/result.tsx | 21 ++------ src/components/search-bar/search-bar.tsx | 7 ++- src/components/stepper/stepper.tsx | 9 ++-- src/components/toast/toast.tsx | 14 ++++-- .../virtual-input/virtual-input.tsx | 7 ++- 21 files changed, 199 insertions(+), 81 deletions(-) create mode 100644 src/components/result/result-icon.tsx diff --git a/src/components/center-popup/center-popup.tsx b/src/components/center-popup/center-popup.tsx index 193600c029..6decbdf003 100644 --- a/src/components/center-popup/center-popup.tsx +++ b/src/components/center-popup/center-popup.tsx @@ -16,6 +16,7 @@ import { defaultPopupBaseProps, PopupBaseProps, } from '../popup/popup-base-props' +import { useConfig } from '../config-provider' const classPrefix = 'adm-center-popup' @@ -39,6 +40,9 @@ const defaultProps = { export const CenterPopup: FC = p => { const props = mergeProps(defaultProps, p) + const { + icons: { CenterPopupClose = CloseOutline }, + } = useConfig() const unmountedRef = useUnmountedRef() const style = useSpring({ @@ -134,7 +138,7 @@ export const CenterPopup: FC = p => { props.onClose?.() }} > - + )} {body} diff --git a/src/components/check-list/check-list.tsx b/src/components/check-list/check-list.tsx index c83efe1365..42ab89b776 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,13 @@ export type CheckListProps = Pick & { const defaultProps = { multiple: false, defaultValue: [], - activeIcon: , } export const CheckList: FC = p => { - const props = mergeProps(defaultProps, p) + const { + icons: { CheckListActive = CheckOutline }, + } = useConfig() + const props = mergeProps({ activeIcon: }, defaultProps, p) const [value, setValue] = usePropsValue(props) diff --git a/src/components/collapse/collapse.tsx b/src/components/collapse/collapse.tsx index 569cb3dc4a..e57e80e9a7 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,9 @@ export type CollapseProps = ( } & NativeProps export const Collapse: FC = props => { + const { + icons: { CollapseArrow = DownOutline }, + } = useConfig() const panels: ReactElement[] = [] traverseReactNode(props.children, child => { if (!isValidElement(child)) return @@ -197,7 +201,7 @@ export const Collapse: FC = props => { } const renderArrow = () => { - let arrow: CollapseProps['arrow'] = + let arrow: CollapseProps['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..2ea2517faf 100644 --- a/src/components/config-provider/config-provider.tsx +++ b/src/components/config-provider/config-provider.tsx @@ -1,10 +1,46 @@ import React, { useContext } from 'react' -import type { FC, ReactNode } from 'react' +import type { ComponentType, FC, ReactNode } from 'react' import { Locale } from '../../locales/base' import zhCN from '../../locales/zh-CN' +import { mergeProps } from '../../utils/with-default-props' + +export type IconMap = Partial< + Record< + | 'CenterPopupClose' + | 'CheckListActive' + | 'CollapseArrow' + | 'DropdownItemArrow' + | 'FormItemHelp' + | 'ImageUploaderUpload' + | 'ImageUploaderDelete' + | 'InputClear' + | 'ListItemArrow' + | 'NavBarBackArrow' + | 'NoticeBarClose' + | 'NoticeBarNotice' + | 'NumberKeyboardClose' + | 'NumberKeyboardDelete' + | 'PopupClose' + | 'ResultSuccess' + | 'ResultError' + | 'ResultInfo' + | 'ResultWaiting' + | 'ResultWarning' + | 'SearchBarSearch' + | 'StepperPlus' + | 'StepperMinus' + | 'ToastSuccess' + | 'ToastFail' + | 'ToastLoading', + ComponentType<{ className?: string }> + > +> + +const defaultIcons: IconMap = {} type Config = { locale: Locale + icons: IconMap } export const defaultConfigRef: { @@ -12,6 +48,7 @@ export const defaultConfigRef: { } = { current: { locale: zhCN, + icons: defaultIcons, }, } @@ -23,9 +60,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 +72,7 @@ export const ConfigProvider: FC = props => { return ( {children} @@ -46,5 +80,5 @@ export const ConfigProvider: FC = props => { } export function useConfig() { - return useContext(ConfigContext) ?? getDefaultConfig() + return useContext(ConfigContext) } diff --git a/src/components/config-provider/tests/config-provider.test.tsx b/src/components/config-provider/tests/config-provider.test.tsx index 8b348b6bc3..3ca42c3ebc 100644 --- a/src/components/config-provider/tests/config-provider.test.tsx +++ b/src/components/config-provider/tests/config-provider.test.tsx @@ -100,7 +100,7 @@ describe('ConfigProvider', () => { }) }) - test('useConfig should only has `locale`', () => { + test('useConfig should only has `locale` and `icons`', () => { let config: ReturnType const Demo = () => { @@ -109,6 +109,6 @@ describe('ConfigProvider', () => { } render() - expect(Object.keys(config!)).toEqual(['locale']) + expect(Object.keys(config!)).toEqual(['locale', 'icons']) }) }) diff --git a/src/components/dropdown/item.tsx b/src/components/dropdown/item.tsx index afa8c5ff16..c1ee98eaf3 100644 --- a/src/components/dropdown/item.tsx +++ b/src/components/dropdown/item.tsx @@ -1,9 +1,10 @@ import classNames from 'classnames' import React from 'react' import type { FC, ReactNode } from 'react' +import { DownFill } from 'antd-mobile-icons' import { NativeProps, withNativeProps } from '../../utils/native-props' import { useShouldRender } from '../../utils/should-render' -import { DownFill } from 'antd-mobile-icons' +import { useConfig } from '../config-provider' const classPrefix = `adm-dropdown-item` @@ -20,6 +21,9 @@ export type DropdownItemProps = { } & NativeProps const Item: FC = props => { + const { + icons: { DropdownItemArrow = DownFill }, + } = useConfig() const cls = classNames(classPrefix, { [`${classPrefix}-active`]: props.active, [`${classPrefix}-highlight`]: props.highlight ?? props.active, @@ -35,7 +39,7 @@ const Item: FC = props => { [`${classPrefix}-title-arrow-active`]: props.active, })} > - {props.arrow === undefined ? : props.arrow} + {props.arrow === undefined ? : props.arrow} diff --git a/src/components/form/form-item.tsx b/src/components/form/form-item.tsx index 8a21f75027..b17a2c351d 100644 --- a/src/components/form/form-item.tsx +++ b/src/components/form/form-item.tsx @@ -110,7 +110,10 @@ const FormItemLayout: FC = props => { const context = useContext(FormContext) - const { locale } = useConfig() + const { + locale, + icons: { FormItemHelp = QuestionCircleOutline }, + } = useConfig() const hasFeedback = props.hasFeedback !== undefined ? props.hasFeedback : context.hasFeedback @@ -162,7 +165,7 @@ const FormItemLayout: FC = props => { e.preventDefault() }} > - + )} diff --git a/src/components/image-uploader/image-uploader.tsx b/src/components/image-uploader/image-uploader.tsx index 699218b142..23249ce22a 100644 --- a/src/components/image-uploader/image-uploader.tsx +++ b/src/components/image-uploader/image-uploader.tsx @@ -78,7 +78,6 @@ const classPrefix = `adm-image-uploader` const defaultProps = { disableUpload: false, deletable: true, - deleteIcon: , showUpload: true, multiple: false, maxCount: 0, @@ -91,8 +90,23 @@ const defaultProps = { export const ImageUploader = forwardRef( (p, ref) => { - const { locale } = useConfig() - const props = mergeProps(defaultProps, p) + const { + locale, + icons: { + ImageUploaderDelete = CloseOutline, + ImageUploaderUpload = AddOutline, + }, + } = useConfig() + + const props = mergeProps( + { + deleteIcon: ( + + ), + }, + defaultProps, + p + ) const { columns } = props const [value, setValue] = usePropsValue(props) @@ -312,7 +326,7 @@ export const ImageUploader = forwardRef( aria-label={locale.ImageUploader.upload} > - + )} diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx index 94d8dfdc78..3e2dc7bf42 100644 --- a/src/components/input/input.tsx +++ b/src/components/input/input.tsx @@ -86,7 +86,10 @@ export const Input = forwardRef((p, ref) => { const [hasFocus, setHasFocus] = useState(false) const compositionStartRef = useRef(false) const nativeInputRef = useRef(null) - const { locale } = useConfig() + const { + locale, + icons: { InputClear = CloseCircleFill }, + } = useConfig() useImperativeHandle(ref, () => ({ clear: () => { @@ -219,7 +222,7 @@ export const Input = forwardRef((p, ref) => { }} aria-label={locale.Input.clear} > - + )} diff --git a/src/components/list/list-item.tsx b/src/components/list/list-item.tsx index d005c59ee1..a28fb5742c 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,9 @@ export type ListItemProps = { > export const ListItem: FC = props => { + const { + icons: { ListItemArrow = RightOutline }, + } = useConfig() const clickable = props.clickable ?? !!props.onClick const arrow = props.arrow === undefined ? clickable : props.arrow @@ -46,7 +50,7 @@ export const ListItem: FC = props => { )} {isNodeWithContent(arrow) && (
- {arrow === true ? : arrow} + {arrow === true ? : arrow}
)} diff --git a/src/components/nav-bar/nav-bar.tsx b/src/components/nav-bar/nav-bar.tsx index 81ff6ef9c9..f0c3af6ca3 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` @@ -20,6 +21,9 @@ const defaultProps = { backArrow: true, } export const NavBar: FC = p => { + const { + icons: { NavBarBackArrow = LeftOutline }, + } = useConfig() const props = mergeProps(defaultProps, p) const { back, backArrow } = props @@ -31,7 +35,7 @@ export const NavBar: FC = p => {
{backArrow && ( - {backArrow === true ? : backArrow} + {backArrow === true ? : backArrow} )} diff --git a/src/components/notice-bar/notice-bar.tsx b/src/components/notice-bar/notice-bar.tsx index 655cb462cd..c33135d848 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` @@ -45,11 +46,13 @@ const defaultProps = { delay: 2000, speed: 50, wrap: false, - icon: , } export const NoticeBar = memo(p => { - const props = mergeProps(defaultProps, p) + const { + icons: { NoticeBarClose = CloseOutline, NoticeBarNotice = SoundOutline }, + } = useConfig() + const props = mergeProps({ icon: }, defaultProps, p) const containerRef = useRef(null) const textRef = useRef(null) @@ -149,7 +152,7 @@ export const NoticeBar = memo(p => { props.onClose?.() }} > - +
)} diff --git a/src/components/number-keyboard/number-keyboard.tsx b/src/components/number-keyboard/number-keyboard.tsx index ab4ecac484..f893aaecd4 100644 --- a/src/components/number-keyboard/number-keyboard.tsx +++ b/src/components/number-keyboard/number-keyboard.tsx @@ -60,7 +60,13 @@ export const NumberKeyboard: FC = p => { onInput, } = props - const { locale } = useConfig() + const { + locale, + icons: { + NumberKeyboardClose = DownOutline, + NumberKeyboardDelete = TextDeletionOutline, + }, + } = useConfig() const keyboardRef = useRef(null) @@ -145,7 +151,7 @@ export const NumberKeyboard: FC = p => { title={locale.common.close} tabIndex={-1} > - + )} @@ -186,7 +192,7 @@ export const NumberKeyboard: FC = p => { }} {...ariaProps} > - {key === 'BACKSPACE' ? : key} + {key === 'BACKSPACE' ? : key} ) } @@ -240,7 +246,7 @@ export const NumberKeyboard: FC = p => { role='button' tabIndex={-1} > - +
= p => { `${classPrefix}-body-position-${props.position}` ) - const { locale } = useConfig() + const { + locale, + icons: { PopupClose = CloseOutline }, + } = useConfig() const [active, setActive] = useState(props.visible) const ref = useRef(null) useLockScroll(ref, props.disableBodyScroll && active ? 'strict' : false) @@ -156,7 +159,7 @@ export const Popup: FC = p => { role='button' aria-label={locale.common.close} > - + )} {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..f1f107f76b --- /dev/null +++ b/src/components/result/result-icon.tsx @@ -0,0 +1,39 @@ +import React, { FC } 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 { + icons: { + ResultSuccess = CheckCircleFill, + ResultError = CloseCircleFill, + ResultInfo = InformationCircleFill, + ResultWaiting = ClockCircleFill, + ResultWarning = ExclamationCircleFill, + }, + } = useConfig() + switch (status) { + case 'success': + return + case 'error': + return + case 'info': + return + case 'waiting': + return + case 'warning': + return + 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/search-bar/search-bar.tsx b/src/components/search-bar/search-bar.tsx index 0614f7207f..f88252a470 100644 --- a/src/components/search-bar/search-bar.tsx +++ b/src/components/search-bar/search-bar.tsx @@ -44,15 +44,18 @@ const defaultProps = { showCancelButton: false as NonNullable, defaultValue: '', clearOnCancel: true, - icon: , } export const SearchBar = forwardRef((p, ref) => { - const { locale } = useConfig() + const { + locale, + icons: { SearchBarSearch = SearchOutline }, + } = useConfig() const props = mergeProps( defaultProps, { cancelText: locale.common.cancel, + icon: , }, p ) diff --git a/src/components/stepper/stepper.tsx b/src/components/stepper/stepper.tsx index 8c1b0f1a03..0ad67d4df0 100644 --- a/src/components/stepper/stepper.tsx +++ b/src/components/stepper/stepper.tsx @@ -112,7 +112,10 @@ export function InnerStepper( parser, } = props as MergedStepperProps - const { locale } = useConfig() + const { + locale, + icons: { StepperPlus = AddOutline, StepperMinus = MinusOutline }, + } = useConfig() // ========================== Ref ========================== useImperativeHandle(ref, () => ({ @@ -298,7 +301,7 @@ export function InnerStepper( color='primary' aria-label={locale.Stepper.decrease} > - +
( color='primary' aria-label={locale.Stepper.increase} > - +
) diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx index b9e23ac7a9..7e1a23f043 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,13 @@ const defaultProps = { } export const InternalToast: FC = p => { + const { + icons: { + ToastSuccess = CheckOutline, + ToastFail = CloseOutline, + ToastLoading = SpinLoading, + }, + } = useConfig() const props = mergeProps(defaultProps, p) const { maskClickable, content, icon, position } = props @@ -39,12 +47,12 @@ export const InternalToast: FC = p => { if (icon === null || icon === undefined) return null switch (icon) { case 'success': - return + return case 'fail': - return + return case 'loading': return ( - + ) default: return icon diff --git a/src/components/virtual-input/virtual-input.tsx b/src/components/virtual-input/virtual-input.tsx index 955496f4b0..0a7bc8c877 100644 --- a/src/components/virtual-input/virtual-input.tsx +++ b/src/components/virtual-input/virtual-input.tsx @@ -52,7 +52,10 @@ export const VirtualInput = forwardRef( const rootRef = useRef(null) const contentRef = useRef(null) const [hasFocus, setHasFocus] = useState(false) - const { locale } = useConfig() + const { + locale, + icons: { InputClear = CloseCircleFill }, + } = useConfig() function scrollToEnd() { const root = rootRef.current @@ -157,7 +160,7 @@ export const VirtualInput = forwardRef( role='button' aria-label={locale.Input.clear} > - +
)} {[undefined, null, ''].includes(value) && (