From 7d233ea995d748203d8727b3b87755e1bce81f01 Mon Sep 17 00:00:00 2001 From: raylotan Date: Fri, 9 Aug 2024 21:03:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(tag):=20=E7=A4=BA=E4=BE=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=96=87=E4=BB=B6=E6=94=B9=E4=B8=BAtsx=EF=BC=8Cprops?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=BB=98=E8=AE=A4=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/mobile/mobile.config.js | 2 +- src/_util/parseTNode.ts | 39 +++++ src/tag/CheckTag.tsx | 62 +++---- src/tag/Tag.tsx | 154 +++++++++--------- .../_example/{checkable.jsx => checkable.tsx} | 0 .../_example/{closable.jsx => closable.tsx} | 0 src/tag/_example/{index.jsx => index.tsx} | 0 src/tag/_example/{size.jsx => size.tsx} | 0 src/tag/_example/{theme.jsx => theme.tsx} | 0 src/tag/_example/{type.jsx => type.tsx} | 2 +- src/tag/defaultProps.ts | 23 +++ 11 files changed, 176 insertions(+), 106 deletions(-) create mode 100644 src/_util/parseTNode.ts rename src/tag/_example/{checkable.jsx => checkable.tsx} (100%) rename src/tag/_example/{closable.jsx => closable.tsx} (100%) rename src/tag/_example/{index.jsx => index.tsx} (100%) rename src/tag/_example/{size.jsx => size.tsx} (100%) rename src/tag/_example/{theme.jsx => theme.tsx} (100%) rename src/tag/_example/{type.jsx => type.tsx} (96%) create mode 100644 src/tag/defaultProps.ts diff --git a/site/mobile/mobile.config.js b/site/mobile/mobile.config.js index 4bb613b7a..eaf2bb9a5 100644 --- a/site/mobile/mobile.config.js +++ b/site/mobile/mobile.config.js @@ -145,7 +145,7 @@ export default { { title: 'Tag 标签', name: 'tag', - component: () => import('tdesign-mobile-react/tag/_example/index.jsx'), + component: () => import('tdesign-mobile-react/tag/_example/index.tsx'), }, { title: 'Toast 轻提示', diff --git a/src/_util/parseTNode.ts b/src/_util/parseTNode.ts new file mode 100644 index 000000000..6dbd30725 --- /dev/null +++ b/src/_util/parseTNode.ts @@ -0,0 +1,39 @@ +import isFunction from 'lodash/isFunction'; +import React, { ReactElement, ReactNode } from 'react'; +import log from '../_common/js/log'; +import { TNode } from '../common'; + +// 解析 TNode 数据结构 +export default function parseTNode( + renderNode: TNode | TNode | undefined, + renderParams?: any, + defaultNode?: ReactNode, +): ReactNode { + let node: ReactNode = null; + + if (typeof renderNode === 'function') { + node = renderNode(renderParams); + } else if (renderNode === true) { + node = defaultNode; + } else if (renderNode !== null) { + node = renderNode ?? defaultNode; + } + return node as ReactNode; +} + +/** + * 解析各种数据类型的 TNode + * 函数类型:content={(props) => } + * 组件类型:content={} 这种方式可以避免函数重复渲染,对应的 props 已经注入 + * 字符类型 + */ +export function parseContentTNode(tnode: TNode, props: T) { + if (isFunction(tnode)) return tnode(props) as ReactNode; + if (!tnode || ['string', 'number', 'boolean'].includes(typeof tnode)) return tnode as ReactNode; + try { + return React.cloneElement(tnode as ReactElement, { ...props }); + } catch (e) { + log.warn('parseContentTNode', `${tnode} is not a valid ReactNode`); + return null; + } +} diff --git a/src/tag/CheckTag.tsx b/src/tag/CheckTag.tsx index 909553bf6..bdadafb19 100644 --- a/src/tag/CheckTag.tsx +++ b/src/tag/CheckTag.tsx @@ -1,40 +1,44 @@ import classNames from 'classnames'; -import React, { forwardRef, Ref, type MouseEvent } from 'react'; +import React, { + forwardRef, + memo +} from 'react'; import { Icon } from 'tdesign-icons-react'; -import noop from '../_util/noop'; +import { StyledProps } from 'tdesign-mobile-react/common'; import useConfig from '../_util/useConfig'; -import useDefault, { ChangeHandler } from '../_util/useDefault'; +import useDefault from '../_util/useDefault'; +import useDefaultProps from '../hooks/useDefaultProps'; +import { checkTagDefaultProps } from "./defaultProps"; import { TdCheckTagProps } from './type'; -export interface TagCheckProps extends TdCheckTagProps { - className?: string; - style?: object; -} +export interface TagCheckProps extends TdCheckTagProps, StyledProps { } -const TagCheck: React.FC = React.memo( - forwardRef((props, ref: Ref) => { + +const TagCheck: React.FC = memo( + forwardRef((originProps, ref) => { + const props = useDefaultProps(originProps, checkTagDefaultProps) const { - checked = undefined, - defaultChecked = undefined, - content = '', + checked, + defaultChecked, + content, children, - style = {}, - className = '', - icon = '', - disabled = false, - closable = false, - size = 'medium', - shape = 'square', - variant = 'dark', - onClick = noop, - onChange = noop, - onClose = noop, - ...other + style, + className, + icon, + disabled, + closable, + size, + shape, + variant, + onClick, + onChange, + onClose, + ...otherProps } = props; const { classPrefix } = useConfig(); - const [innerChecked, onInnerChecked] = useDefault(checked, defaultChecked, onChange as ChangeHandler); + const [innerChecked, onInnerChecked] = useDefault(checked, defaultChecked, onChange); const baseClass = `${classPrefix}-tag`; @@ -65,15 +69,15 @@ const TagCheck: React.FC = React.memo( ...style, }; - const handleClick = (e: MouseEvent) => { + const handleClick = (e) => { if (!props.disabled) { - onClick?.({ e }); + onClick?.(e); onInnerChecked(!innerChecked); } }; - const handleClose = (e: MouseEvent): void => { + const handleClose = (e) => { e.stopPropagation(); if (!props.disabled) { onClose?.({ e }); @@ -88,7 +92,7 @@ const TagCheck: React.FC = React.memo( role="button" onClick={handleClick} style={tagStyle} - {...other} + {...otherProps} > {icon && {icon}} {renderText() || children} diff --git a/src/tag/Tag.tsx b/src/tag/Tag.tsx index ff736a5c9..ceb8ebab0 100644 --- a/src/tag/Tag.tsx +++ b/src/tag/Tag.tsx @@ -1,88 +1,92 @@ import classNames from 'classnames'; -import React, { forwardRef, type MouseEvent } from 'react'; +import React, { forwardRef, memo } from 'react'; import { Icon } from 'tdesign-icons-react'; -import noop from '../_util/noop'; +import parseTNode from 'tdesign-mobile-react/_util/parseTNode'; +import { StyledProps } from 'tdesign-mobile-react/common'; +import useDefaultProps from 'tdesign-mobile-react/hooks/useDefaultProps'; import useConfig from '../_util/useConfig'; +import { tagDefaultProps } from './defaultProps'; import { TdTagProps } from './type'; -export interface TagProps extends TdTagProps { - className: string; - style: object; -} +export interface TagProps extends TdTagProps, StyledProps { } -const Tag = forwardRef((props, ref) => { - const { - className = '', - style = {}, - closable = false, - content = null, - disabled = false, - icon = undefined, - maxWidth, - children = '', - shape = 'square', - size = 'medium', - theme = 'default', - variant = 'dark', - onClick = noop, - onClose = noop, - ...other - } = props; +const Tag: React.FC = memo( + forwardRef((originProps, ref) => { + const props = useDefaultProps(originProps, tagDefaultProps) + const { + className, + style, + closable, + content, + disabled, + icon, + maxWidth, + children, + shape, + size, + theme, + variant, + onClick, + onClose, + ...otherProps + } = props; - const { classPrefix } = useConfig(); - const baseClass = `${classPrefix}-tag`; + const { classPrefix } = useConfig(); + const baseClass = `${classPrefix}-tag`; - const tagClassNames = classNames( - `${baseClass}`, - `${baseClass}--${theme}`, - `${baseClass}--${shape}`, - `${baseClass}--${variant}`, - `${baseClass}--${size}`, - { - [`${classPrefix}-is-closable ${baseClass}--closable`]: closable, - [`${classPrefix}-is-disabled ${baseClass}--disabled`]: disabled, - }, - className, - ); + const tagClassNames = classNames( + `${baseClass}`, + `${baseClass}--${theme}`, + `${baseClass}--${shape}`, + `${baseClass}--${variant}`, + `${baseClass}--${size}`, + { + [`${classPrefix}-is-closable ${baseClass}--closable`]: closable, + [`${classPrefix}-is-disabled ${baseClass}--disabled`]: disabled, + }, + className, + ); - const tagStyle = { - ...style, - maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth, - }; + const tagStyle = { + ...style, + maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth, + }; - const handleClose = (e: MouseEvent): void => { - e.stopPropagation(); - if (!props.disabled) { - onClose?.({ e }); - } - }; + const handleClose = (e) => { + e.stopPropagation(); + if (!props.disabled) { + onClose?.({ e }); + } + }; - const handleClick = (e: MouseEvent) => { - if (disabled) { - return; - } - onClick?.({ e }); - }; + const handleClick = (e) => { + if (disabled) { + return; + } + onClick?.({ e }); + }; - return ( - - {icon && {icon}} - {content || children} - {props.closable && ( - - - - )} - - ) -}); + const ChildNode = parseTNode(content) || parseTNode(children); -export default React.memo(Tag); + return ( + + {icon && {icon}} + {ChildNode} + {props.closable && ( + + + + )} + + ) + })); + +export default Tag; diff --git a/src/tag/_example/checkable.jsx b/src/tag/_example/checkable.tsx similarity index 100% rename from src/tag/_example/checkable.jsx rename to src/tag/_example/checkable.tsx diff --git a/src/tag/_example/closable.jsx b/src/tag/_example/closable.tsx similarity index 100% rename from src/tag/_example/closable.jsx rename to src/tag/_example/closable.tsx diff --git a/src/tag/_example/index.jsx b/src/tag/_example/index.tsx similarity index 100% rename from src/tag/_example/index.jsx rename to src/tag/_example/index.tsx diff --git a/src/tag/_example/size.jsx b/src/tag/_example/size.tsx similarity index 100% rename from src/tag/_example/size.jsx rename to src/tag/_example/size.tsx diff --git a/src/tag/_example/theme.jsx b/src/tag/_example/theme.tsx similarity index 100% rename from src/tag/_example/theme.jsx rename to src/tag/_example/theme.tsx diff --git a/src/tag/_example/type.jsx b/src/tag/_example/type.tsx similarity index 96% rename from src/tag/_example/type.jsx rename to src/tag/_example/type.tsx index 785a243ec..2fce5361a 100644 --- a/src/tag/_example/type.jsx +++ b/src/tag/_example/type.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Icon } from 'tdesign-icons-react'; -import { Tag } from 'tdesign-mobile-react'; +import { Tag } from "tdesign-mobile-react"; const TypeDemo = () => ( <> diff --git a/src/tag/defaultProps.ts b/src/tag/defaultProps.ts new file mode 100644 index 000000000..fba81011f --- /dev/null +++ b/src/tag/defaultProps.ts @@ -0,0 +1,23 @@ +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdCheckTagProps, TdTagProps } from './type'; + +export const tagDefaultProps: TdTagProps = { + closable: false, + disabled: false, + icon: undefined, + shape: 'square', + size: 'medium', + theme: 'default', + variant: 'dark', +}; + +export const checkTagDefaultProps: TdCheckTagProps = { + closable: false, + disabled: false, + shape: 'square', + size: 'medium', + variant: 'dark', +};