Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve(design): Typography editable and editing style #839

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/design/src/typography/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Link';
export interface LinkProps extends AntLinkProps {}

const Link = React.forwardRef<HTMLElement, LinkProps>(
({ 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(
<AntLink ref={ref} prefixCls={customizePrefixCls} {...restProps}>
<AntLink
ref={ref}
editable={editable}
prefixCls={customizePrefixCls}
className={typographyCls}
{...restProps}
>
{children}
</AntLink>
);
Expand Down
12 changes: 10 additions & 2 deletions packages/design/src/typography/Paragraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Paragraph';
export interface ParagraphProps extends AntParagraphProps {}

const Paragraph = React.forwardRef<HTMLElement, ParagraphProps>(
({ 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(
<AntParagraph ref={ref} prefixCls={customizePrefixCls} {...restProps}>
<AntParagraph
ref={ref}
editable={editable}
prefixCls={customizePrefixCls}
className={typographyCls}
{...restProps}
>
{children}
</AntParagraph>
);
Expand Down
12 changes: 10 additions & 2 deletions packages/design/src/typography/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Text';
export interface TextProps extends AntTextProps {}

const Text = React.forwardRef<HTMLSpanElement, TextProps>(
({ 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(
<AntText ref={ref} prefixCls={customizePrefixCls} {...restProps}>
<AntText
ref={ref}
editable={editable}
prefixCls={customizePrefixCls}
className={typographyCls}
{...restProps}
>
{children}
</AntText>
);
Expand Down
12 changes: 10 additions & 2 deletions packages/design/src/typography/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,13 +12,20 @@ export * from 'antd/es/typography/Title';
export interface TitleProps extends AntTitleProps {}

const Title = React.forwardRef<HTMLElement, TitleProps>(
({ 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(
<AntTitle ref={ref} prefixCls={customizePrefixCls} {...restProps}>
<AntTitle
ref={ref}
editable={editable}
prefixCls={customizePrefixCls}
className={typographyCls}
{...restProps}
>
{children}
</AntTitle>
);
Expand Down
155 changes: 155 additions & 0 deletions packages/design/src/typography/demo/editable.tsx
Original file line number Diff line number Diff line change
@@ -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<string>(() => {
if (chooseTrigger.includes('text')) {
return chooseTrigger.includes('icon') ? 'both' : 'text';
}
return 'icon';
}, [chooseTrigger]);

return (
<>
<div style={{ marginBottom: 12 }}>
Trigger edit with:{' '}
<Radio.Group
onChange={e => setChooseTrigger(radioToState(e.target.value))}
value={stateToRadio}
>
<Radio value="icon">icon</Radio>
<Radio value="text">text</Radio>
<Radio value="both">both</Radio>
</Radio.Group>
</div>
<Paragraph editable={{ onChange: setEditableStr, triggerType: chooseTrigger }}>
{editableStr}
</Paragraph>
<Paragraph
editable={{
onChange: setEditableStrWithSuffix,
text: editableStrWithSuffix,
triggerType: chooseTrigger,
}}
ellipsis={{
suffix: editableStrWithSuffixSuffixPart,
}}
>
{editableStrWithSuffixStartPart}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
tooltip: 'click to edit text',
onChange: setCustomIconStr,
triggerType: chooseTrigger,
}}
>
{customIconStr}
</Paragraph>
<Paragraph
editable={{
tooltip: 'click to edit text',
onChange: setClickTriggerStr,
triggerType: chooseTrigger,
}}
>
{clickTriggerStr}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
tooltip: 'click to edit text',
onChange: setCustomEnterIconStr,
triggerType: chooseTrigger,
enterIcon: <CheckOutlined />,
}}
>
{customEnterIconStr}
</Paragraph>
<Paragraph
editable={{
icon: <HighlightOutlined />,
tooltip: 'click to edit text',
onChange: setNoEnterIconStr,
triggerType: chooseTrigger,
enterIcon: null,
}}
>
{noEnterIconStr}
</Paragraph>
<Paragraph
editable={{ tooltip: false, onChange: setHideTooltipStr, triggerType: chooseTrigger }}
>
{hideTooltipStr}
</Paragraph>
<Paragraph
editable={{
onChange: setLengthLimitedStr,
triggerType: chooseTrigger,
maxLength: 50,
autoSize: { maxRows: 5, minRows: 3 },
}}
>
{lengthLimitedStr}
</Paragraph>
<Typography.Title editable={{ triggerType: chooseTrigger }} level={1} style={{ margin: 0 }}>
h1. Ant Design
</Typography.Title>
<Typography.Title editable={{ triggerType: chooseTrigger }} level={2} style={{ margin: 0 }}>
h2. Ant Design
</Typography.Title>
<Typography.Title editable={{ triggerType: chooseTrigger }} level={3} style={{ margin: 0 }}>
h3. Ant Design
</Typography.Title>
<Typography.Title editable={{ triggerType: chooseTrigger }} level={4} style={{ margin: 0 }}>
h4. Ant Design
</Typography.Title>
<Typography.Title editable={{ triggerType: chooseTrigger }} level={5} style={{ margin: 0 }}>
h5. Ant Design
</Typography.Title>
</>
);
};

export default App;
17 changes: 17 additions & 0 deletions packages/design/src/typography/hooks/useClassName.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 2 additions & 0 deletions packages/design/src/typography/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ nav:

<code src="./demo/text.tsx" title="文本与超链接"></code>

<code src="./demo/editable.tsx" title="可编辑"></code>

<code src="./demo/font-family.tsx" title="字体" description="详见 [字体设计规范](/docs/spec/typography)。"></code>

<code src="./demo/inner.tsx" title="和其他组件组合使用" debug></code>
Expand Down
40 changes: 39 additions & 1 deletion packages/design/src/typography/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export type TypographyToken = FullToken<'Typography'>;
export const genTypographyStyle: GenerateStyle<TypographyToken> = (
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
Expand All @@ -21,6 +22,43 @@ export const genTypographyStyle: GenerateStyle<TypographyToken> = (
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`,
},
},
};
};

Expand Down
Loading