From fedc38ddc9faf8dfe439b7d5760b83e800f79959 Mon Sep 17 00:00:00 2001 From: dengfuping Date: Tue, 19 Nov 2024 15:17:00 +0800 Subject: [PATCH] improve(design): Typography editable and editing style --- packages/design/src/typography/Link.tsx | 12 +- packages/design/src/typography/Paragraph.tsx | 12 +- packages/design/src/typography/Text.tsx | 12 +- packages/design/src/typography/Title.tsx | 12 +- .../design/src/typography/demo/editable.tsx | 155 ++++++++++++++++++ .../src/typography/hooks/useClassName.ts | 17 ++ packages/design/src/typography/index.md | 2 + packages/design/src/typography/style/index.ts | 40 ++++- 8 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 packages/design/src/typography/demo/editable.tsx create mode 100644 packages/design/src/typography/hooks/useClassName.ts diff --git a/packages/design/src/typography/Link.tsx b/packages/design/src/typography/Link.tsx index 65476132f..00872fd21 100644 --- a/packages/design/src/typography/Link.tsx +++ b/packages/design/src/typography/Link.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Typography as AntTypography } from 'antd'; import type { LinkProps as AntLinkProps } from 'antd/es/typography/Link'; import ConfigProvider from '../config-provider'; +import useClassName from './hooks/useClassName'; import useStyle from './style'; const { Link: AntLink } = AntTypography; @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Link'; export interface LinkProps extends AntLinkProps {} const Link = React.forwardRef( - ({ prefixCls: customizePrefixCls, children, ...restProps }, ref) => { + ({ editable, prefixCls: customizePrefixCls, className, children, ...restProps }, ref) => { const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('typography', customizePrefixCls); const { wrapSSR } = useStyle(prefixCls); + const typographyCls = useClassName(prefixCls, className, editable); return wrapSSR( - + {children} ); diff --git a/packages/design/src/typography/Paragraph.tsx b/packages/design/src/typography/Paragraph.tsx index f906c3e19..6add47b7f 100644 --- a/packages/design/src/typography/Paragraph.tsx +++ b/packages/design/src/typography/Paragraph.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Typography as AntTypography } from 'antd'; import type { ParagraphProps as AntParagraphProps } from 'antd/es/typography/Paragraph'; import ConfigProvider from '../config-provider'; +import useClassName from './hooks/useClassName'; import useStyle from './style'; const { Paragraph: AntParagraph } = AntTypography; @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Paragraph'; export interface ParagraphProps extends AntParagraphProps {} const Paragraph = React.forwardRef( - ({ prefixCls: customizePrefixCls, children, ...restProps }, ref) => { + ({ editable, prefixCls: customizePrefixCls, className, children, ...restProps }, ref) => { const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('typography', customizePrefixCls); const { wrapSSR } = useStyle(prefixCls); + const typographyCls = useClassName(prefixCls, className, editable); return wrapSSR( - + {children} ); diff --git a/packages/design/src/typography/Text.tsx b/packages/design/src/typography/Text.tsx index e2e26a43a..356c7b9e0 100644 --- a/packages/design/src/typography/Text.tsx +++ b/packages/design/src/typography/Text.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Typography as AntTypography } from 'antd'; import type { TextProps as AntTextProps } from 'antd/es/typography/Text'; import ConfigProvider from '../config-provider'; +import useClassName from './hooks/useClassName'; import useStyle from './style'; const { Text: AntText } = AntTypography; @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Text'; export interface TextProps extends AntTextProps {} const Text = React.forwardRef( - ({ prefixCls: customizePrefixCls, children, ...restProps }, ref) => { + ({ editable, prefixCls: customizePrefixCls, className, children, ...restProps }, ref) => { const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('typography', customizePrefixCls); const { wrapSSR } = useStyle(prefixCls); + const typographyCls = useClassName(prefixCls, className, editable); return wrapSSR( - + {children} ); diff --git a/packages/design/src/typography/Title.tsx b/packages/design/src/typography/Title.tsx index 5f14f7620..e186ba784 100644 --- a/packages/design/src/typography/Title.tsx +++ b/packages/design/src/typography/Title.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { Typography as AntTypography } from 'antd'; import type { TitleProps as AntTitleProps } from 'antd/es/typography/Title'; import ConfigProvider from '../config-provider'; +import useClassName from './hooks/useClassName'; import useStyle from './style'; const { Title: AntTitle } = AntTypography; @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Title'; export interface TitleProps extends AntTitleProps {} const Title = React.forwardRef( - ({ prefixCls: customizePrefixCls, children, ...restProps }, ref) => { + ({ editable, prefixCls: customizePrefixCls, className, children, ...restProps }, ref) => { const { getPrefixCls } = useContext(ConfigProvider.ConfigContext); const prefixCls = getPrefixCls('typography', customizePrefixCls); const { wrapSSR } = useStyle(prefixCls); + const typographyCls = useClassName(prefixCls, className, editable); return wrapSSR( - + {children} ); diff --git a/packages/design/src/typography/demo/editable.tsx b/packages/design/src/typography/demo/editable.tsx new file mode 100644 index 000000000..9e26369a4 --- /dev/null +++ b/packages/design/src/typography/demo/editable.tsx @@ -0,0 +1,155 @@ +import React, { useMemo, useState } from 'react'; +import { CheckOutlined, HighlightOutlined } from '@oceanbase/icons'; +import { Radio, Typography } from '@oceanbase/design'; + +const { Paragraph } = Typography; + +const App: React.FC = () => { + const [editableStr, setEditableStr] = useState('This is an editable text.'); + const [editableStrWithSuffix, setEditableStrWithSuffix] = useState( + 'This is a loooooooooooooooooooooooooooooooong editable text with suffix.' + ); + const [editableStrWithSuffixStartPart, editableStrWithSuffixSuffixPart] = useMemo( + () => [editableStrWithSuffix.slice(0, -12), editableStrWithSuffix.slice(-12)], + [editableStrWithSuffix] + ); + const [customIconStr, setCustomIconStr] = useState('Custom Edit icon and replace tooltip text.'); + const [clickTriggerStr, setClickTriggerStr] = useState( + 'Text or icon as trigger - click to start editing.' + ); + const [chooseTrigger, setChooseTrigger] = useState<('icon' | 'text')[]>(['icon']); + const [customEnterIconStr, setCustomEnterIconStr] = useState( + 'Editable text with a custom enter icon in edit field.' + ); + const [noEnterIconStr, setNoEnterIconStr] = useState( + 'Editable text with no enter icon in edit field.' + ); + const [hideTooltipStr, setHideTooltipStr] = useState('Hide Edit tooltip.'); + const [lengthLimitedStr, setLengthLimitedStr] = useState( + 'This is an editable text with limited length.' + ); + + const radioToState = (input: string): ('icon' | 'text')[] => { + switch (input) { + case 'text': + return ['text']; + case 'both': + return ['icon', 'text']; + case 'icon': + return ['icon']; + default: + return ['icon']; + } + }; + + const stateToRadio = useMemo(() => { + if (chooseTrigger.includes('text')) { + return chooseTrigger.includes('icon') ? 'both' : 'text'; + } + return 'icon'; + }, [chooseTrigger]); + + return ( + <> +
+ Trigger edit with:{' '} + setChooseTrigger(radioToState(e.target.value))} + value={stateToRadio} + > + icon + text + both + +
+ + {editableStr} + + + {editableStrWithSuffixStartPart} + + , + tooltip: 'click to edit text', + onChange: setCustomIconStr, + triggerType: chooseTrigger, + }} + > + {customIconStr} + + + {clickTriggerStr} + + , + tooltip: 'click to edit text', + onChange: setCustomEnterIconStr, + triggerType: chooseTrigger, + enterIcon: , + }} + > + {customEnterIconStr} + + , + tooltip: 'click to edit text', + onChange: setNoEnterIconStr, + triggerType: chooseTrigger, + enterIcon: null, + }} + > + {noEnterIconStr} + + + {hideTooltipStr} + + + {lengthLimitedStr} + + + h1. Ant Design + + + h2. Ant Design + + + h3. Ant Design + + + h4. Ant Design + + + h5. Ant Design + + + ); +}; + +export default App; diff --git a/packages/design/src/typography/hooks/useClassName.ts b/packages/design/src/typography/hooks/useClassName.ts new file mode 100644 index 000000000..e07a92dbc --- /dev/null +++ b/packages/design/src/typography/hooks/useClassName.ts @@ -0,0 +1,17 @@ +import React from 'react'; +import type { BlockProps } from 'antd/es/typography/Base'; +import classNames from 'classnames'; + +const useClassName = (prefixCls: string, className: string, editable?: BlockProps['editable']) => { + const typographyCls = classNames( + prefixCls, + { + [`${prefixCls}-editable-text`]: + typeof editable === 'object' && editable?.triggerType?.includes('text'), + }, + className + ); + return typographyCls; +}; + +export default useClassName; diff --git a/packages/design/src/typography/index.md b/packages/design/src/typography/index.md index 5a84a8ef4..4c1ca7cc9 100644 --- a/packages/design/src/typography/index.md +++ b/packages/design/src/typography/index.md @@ -15,6 +15,8 @@ nav: + + diff --git a/packages/design/src/typography/style/index.ts b/packages/design/src/typography/style/index.ts index 80846dee0..9f6f539ea 100644 --- a/packages/design/src/typography/style/index.ts +++ b/packages/design/src/typography/style/index.ts @@ -7,7 +7,8 @@ export type TypographyToken = FullToken<'Typography'>; export const genTypographyStyle: GenerateStyle = ( token: TypographyToken ): CSSObject => { - const { componentCls } = token; + const { componentCls, controlHeight, fontSize, lineHeight } = token; + const marginOffset = (controlHeight - fontSize * lineHeight) / 2; return { // inherit color and lineHeight from parent instead of fixed colorText @@ -21,6 +22,43 @@ export const genTypographyStyle: GenerateStyle = ( color: 'inherit', fontSize: 'inherit', }, + [`${componentCls}`]: { + [`${componentCls}-edit`]: { + fontSize: token.fontSize, + }, + }, + [`${componentCls}${componentCls}-editable-text:not(${componentCls}-edit-content)`]: { + '&:hover': { + background: token.colorBgContainer, + border: `${token.lineWidth}px ${token.lineType} ${token.colorBorder}`, + borderRadius: token.borderRadius, + position: 'relative', + insetInlineStart: -token.paddingSM, + padding: `${marginOffset - token.lineWidth}px ${token.paddingSM - token.lineWidth}px`, + }, + 'div&:hover': { + height: token.controlHeight, + marginTop: -marginOffset, + marginBottom: `calc(1em - ${marginOffset}px)`, + }, + 'h1&:hover, h2&:hover, h3&:hover, h4&:hover, h5&:hover': { + marginTop: `${-marginOffset}px !important`, + marginBottom: `${-marginOffset}px !important`, + }, + }, + [`${componentCls}${componentCls}-edit-content`]: { + 'div&': { + insetInlineStart: -token.paddingSM, + marginTop: -marginOffset, + marginBottom: `calc(1em - ${marginOffset}px)`, + }, + [`${componentCls}-h1&, ${componentCls}-h2&, ${componentCls}-h3&, ${componentCls}-h4&, ${componentCls}-h5&`]: + { + insetInlineStart: -token.paddingSM, + marginTop: `${-marginOffset}px !important`, + marginBottom: `${-marginOffset}px !important`, + }, + }, }; };