mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-13 12:59:19 +08:00
Compare commits
3 Commits
feature
...
typography
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38f32d5cf7 | ||
|
|
065ff3d2ad | ||
|
|
08a0e26973 |
@@ -8,9 +8,13 @@ import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
|
||||
import { composeRef } from '@rc-component/util/lib/ref';
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
import { useMergeSemantic } from '../../_util/hooks';
|
||||
import type { SemanticType } from '../../_util/hooks';
|
||||
import isNonNullable from '../../_util/isNonNullable';
|
||||
import { isStyleSupport } from '../../_util/styleChecker';
|
||||
import type { DirectionType } from '../../config-provider';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
import { useComponentConfig } from '../../config-provider/context';
|
||||
import useLocale from '../../locale/useLocale';
|
||||
import type { TooltipProps } from '../../tooltip';
|
||||
import Tooltip from '../../tooltip';
|
||||
@@ -28,6 +32,41 @@ import { isEleEllipsis, isValidText } from './util';
|
||||
|
||||
export type BaseType = 'secondary' | 'success' | 'warning' | 'danger';
|
||||
|
||||
// Base typography props without generic parameter for semantic types
|
||||
export interface BaseTypographyProps extends React.HTMLAttributes<HTMLElement> {
|
||||
id?: string;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
style?: React.CSSProperties;
|
||||
classNames?: TypographyClassNamesType;
|
||||
styles?: TypographyStylesType;
|
||||
children?: React.ReactNode;
|
||||
'aria-label'?: string;
|
||||
direction?: DirectionType;
|
||||
/** @private */
|
||||
component?: keyof JSX.IntrinsicElements;
|
||||
}
|
||||
|
||||
export type TypographySemanticClassNames = {
|
||||
root?: string;
|
||||
actions?: string;
|
||||
action?: string;
|
||||
};
|
||||
|
||||
export type TypographySemanticStyles = {
|
||||
root?: React.CSSProperties;
|
||||
actions?: React.CSSProperties;
|
||||
action?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export type TypographyClassNamesType = SemanticType<
|
||||
BaseTypographyProps,
|
||||
TypographySemanticClassNames
|
||||
>;
|
||||
|
||||
export type TypographyStylesType = SemanticType<BaseTypographyProps, TypographySemanticStyles>;
|
||||
|
||||
export interface CopyConfig {
|
||||
text?: string | (() => string | Promise<string>);
|
||||
onCopy?: (event?: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
@@ -65,9 +104,8 @@ export interface EllipsisConfig {
|
||||
tooltip?: React.ReactNode | TooltipProps;
|
||||
}
|
||||
|
||||
export interface BlockProps<
|
||||
C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements,
|
||||
> extends TypographyProps<C> {
|
||||
export interface BlockProps<C extends keyof JSX.IntrinsicElements = keyof JSX.IntrinsicElements>
|
||||
extends TypographyProps<C> {
|
||||
title?: string;
|
||||
editable?: boolean | EditConfig;
|
||||
copyable?: boolean | CopyConfig;
|
||||
@@ -126,6 +164,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
style,
|
||||
classNames,
|
||||
styles,
|
||||
type,
|
||||
disabled,
|
||||
children,
|
||||
@@ -141,6 +181,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const [textLocale] = useLocale('Text');
|
||||
|
||||
const { classNames: contextClassNames, styles: contextStyles } = useComponentConfig('typography');
|
||||
|
||||
const typographyRef = React.useRef<HTMLElement>(null);
|
||||
const editIconRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
@@ -149,6 +191,18 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
|
||||
const textProps = omit(restProps, DECORATION_PROPS);
|
||||
|
||||
// ============================ Semantic ============================
|
||||
const [mergedClassNames, mergedStyles] = useMergeSemantic<
|
||||
TypographyClassNamesType,
|
||||
TypographyStylesType,
|
||||
Partial<BlockProps>
|
||||
>([contextClassNames, classNames], [contextStyles, styles], {
|
||||
props: {
|
||||
...props,
|
||||
prefixCls,
|
||||
},
|
||||
});
|
||||
|
||||
// ========================== Editable ==========================
|
||||
const [enableEdit, editConfig] = useMergedConfig<EditConfig>(editable);
|
||||
const [editing, setEditing] = useControlledState(false, editConfig.editing);
|
||||
@@ -424,7 +478,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
return (
|
||||
<span
|
||||
key="operations"
|
||||
className={`${prefixCls}-actions`}
|
||||
className={clsx(`${prefixCls}-actions`, mergedClassNames.actions)}
|
||||
style={mergedStyles.actions}
|
||||
onMouseEnter={() => setIsHoveringOperations(true)}
|
||||
onMouseLeave={() => setIsHoveringOperations(false)}
|
||||
>
|
||||
@@ -474,6 +529,8 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
|
||||
},
|
||||
className,
|
||||
)}
|
||||
classNames={classNames}
|
||||
styles={styles}
|
||||
prefixCls={customizePrefixCls}
|
||||
style={{
|
||||
...style,
|
||||
|
||||
@@ -3,53 +3,49 @@ import type { JSX } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
import type { DirectionType } from '../config-provider';
|
||||
import { useComponentConfig } from '../config-provider/context';
|
||||
import type { BaseTypographyProps } from './Base';
|
||||
import { useTypographySemantic } from './hooks/useTypographySemantic';
|
||||
import useStyle from './style';
|
||||
|
||||
export interface TypographyProps<C extends keyof JSX.IntrinsicElements>
|
||||
extends React.HTMLAttributes<HTMLElement> {
|
||||
id?: string;
|
||||
prefixCls?: string;
|
||||
extends BaseTypographyProps {
|
||||
component?: C;
|
||||
}
|
||||
|
||||
interface InternalProps {
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
/** @internal */
|
||||
component?: C;
|
||||
'aria-label'?: string;
|
||||
component?: keyof JSX.IntrinsicElements;
|
||||
direction?: DirectionType;
|
||||
mergedClassNames?: any;
|
||||
mergedStyles?: any;
|
||||
prefixCls?: string;
|
||||
contextClassName?: string;
|
||||
contextStyle?: React.CSSProperties;
|
||||
hashId?: string;
|
||||
cssVarCls?: string;
|
||||
}
|
||||
|
||||
interface InternalTypographyProps<C extends keyof JSX.IntrinsicElements>
|
||||
extends TypographyProps<C> {}
|
||||
|
||||
const Typography = React.forwardRef<
|
||||
HTMLElement,
|
||||
InternalTypographyProps<keyof JSX.IntrinsicElements>
|
||||
>((props, ref) => {
|
||||
const InternalTypography = React.forwardRef<HTMLElement, InternalProps>((props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
component: Component = 'article',
|
||||
className,
|
||||
rootClassName,
|
||||
children,
|
||||
direction: typographyDirection,
|
||||
direction,
|
||||
style,
|
||||
mergedClassNames,
|
||||
mergedStyles,
|
||||
prefixCls,
|
||||
contextClassName,
|
||||
contextStyle,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const {
|
||||
getPrefixCls,
|
||||
direction: contextDirection,
|
||||
className: contextClassName,
|
||||
style: contextStyle,
|
||||
} = useComponentConfig('typography');
|
||||
|
||||
const direction = typographyDirection ?? contextDirection;
|
||||
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
||||
|
||||
// Style
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls);
|
||||
const componentClassName = clsx(
|
||||
prefixCls,
|
||||
contextClassName,
|
||||
@@ -58,22 +54,74 @@ const Typography = React.forwardRef<
|
||||
},
|
||||
className,
|
||||
rootClassName,
|
||||
mergedClassNames?.root,
|
||||
hashId,
|
||||
cssVarCls,
|
||||
);
|
||||
|
||||
const mergedStyle: React.CSSProperties = { ...contextStyle, ...style };
|
||||
const mergedStyle: React.CSSProperties = {
|
||||
...contextStyle,
|
||||
...mergedStyles?.root,
|
||||
...style,
|
||||
};
|
||||
|
||||
return (
|
||||
// @ts-expect-error: Expression produces a union type that is too complex to represent.
|
||||
<Component className={componentClassName} style={mergedStyle} ref={ref} {...restProps}>
|
||||
<Component {...restProps} className={componentClassName} style={mergedStyle} ref={ref}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
InternalTypography.displayName = 'InternalTypography';
|
||||
}
|
||||
|
||||
const Typography = React.forwardRef<HTMLElement, TypographyProps<keyof JSX.IntrinsicElements>>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
rootClassName,
|
||||
children,
|
||||
direction: typographyDirection,
|
||||
style,
|
||||
classNames,
|
||||
styles,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const { mergedClassNames, mergedStyles, prefixCls, direction, contextClassName, contextStyle } =
|
||||
useTypographySemantic(customizePrefixCls, classNames, styles, typographyDirection, props);
|
||||
|
||||
const [hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
return (
|
||||
<InternalTypography
|
||||
ref={ref}
|
||||
component="article"
|
||||
className={className}
|
||||
rootClassName={rootClassName}
|
||||
direction={direction}
|
||||
style={style}
|
||||
mergedClassNames={mergedClassNames}
|
||||
mergedStyles={mergedStyles}
|
||||
prefixCls={prefixCls}
|
||||
contextClassName={contextClassName}
|
||||
contextStyle={contextStyle}
|
||||
hashId={hashId}
|
||||
cssVarCls={cssVarCls}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</InternalTypography>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Typography.displayName = 'Typography';
|
||||
}
|
||||
|
||||
export default Typography;
|
||||
export { InternalTypography };
|
||||
|
||||
3
components/typography/__tests__/demo-semantic.test.tsx
Normal file
3
components/typography/__tests__/demo-semantic.test.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { semanticDemoTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
semanticDemoTest('typography');
|
||||
109
components/typography/__tests__/semantic.test.tsx
Normal file
109
components/typography/__tests__/semantic.test.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
import Typography from '..';
|
||||
import { render } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import type { TypographyClassNamesType, TypographyStylesType } from '../Base';
|
||||
|
||||
describe('Typography.Semantic', () => {
|
||||
it('should support classNames and styles as functions', () => {
|
||||
const classNamesFn: TypographyClassNamesType = jest.fn(() => {
|
||||
return { root: 'custom-typography' };
|
||||
});
|
||||
|
||||
const stylesFn: TypographyStylesType = jest.fn(() => {
|
||||
return { root: { color: '#1890ff' } };
|
||||
});
|
||||
|
||||
const { rerender } = render(
|
||||
<Typography classNames={classNamesFn} styles={stylesFn}>
|
||||
Test Typography
|
||||
</Typography>,
|
||||
);
|
||||
|
||||
expect(classNamesFn).toHaveBeenCalled();
|
||||
expect(stylesFn).toHaveBeenCalled();
|
||||
|
||||
const rootElement = document.querySelector<HTMLElement>('.ant-typography');
|
||||
expect(rootElement).toHaveClass('custom-typography');
|
||||
expect(rootElement).toHaveStyle({ color: 'rgb(24, 144, 255)' });
|
||||
|
||||
rerender(
|
||||
<Typography classNames={classNamesFn} styles={stylesFn}>
|
||||
Updated Typography
|
||||
</Typography>,
|
||||
);
|
||||
|
||||
const updatedRootElement = document.querySelector<HTMLElement>('.ant-typography');
|
||||
expect(updatedRootElement).toHaveClass('custom-typography');
|
||||
expect(updatedRootElement).toHaveStyle({ color: 'rgb(24, 144, 255)' });
|
||||
});
|
||||
|
||||
it('should merge context and component classNames and styles', () => {
|
||||
const contextClassNames: TypographyClassNamesType = {
|
||||
root: 'context-root',
|
||||
actions: 'context-actions',
|
||||
};
|
||||
const contextStyles: TypographyStylesType = {
|
||||
root: { padding: '10px' },
|
||||
actions: { margin: '5px' },
|
||||
};
|
||||
const componentClassNames: TypographyClassNamesType = {
|
||||
root: 'component-root',
|
||||
};
|
||||
const componentStyles: TypographyStylesType = {
|
||||
root: { fontSize: '14px' },
|
||||
};
|
||||
|
||||
render(
|
||||
<ConfigProvider>
|
||||
<ConfigProvider
|
||||
typography={
|
||||
{
|
||||
className: undefined,
|
||||
style: undefined,
|
||||
classNames: contextClassNames,
|
||||
styles: contextStyles,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<Typography.Paragraph classNames={componentClassNames} styles={componentStyles} copyable>
|
||||
Test Typography
|
||||
</Typography.Paragraph>
|
||||
</ConfigProvider>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
const rootElement = document.querySelector<HTMLElement>('.ant-typography');
|
||||
const actionsElement = document.querySelector<HTMLElement>('.ant-typography-actions');
|
||||
|
||||
expect(rootElement).toHaveClass(clsx(contextClassNames.root, componentClassNames.root));
|
||||
expect(actionsElement).toHaveClass(contextClassNames.actions!);
|
||||
|
||||
expect(rootElement).toHaveStyle({
|
||||
padding: contextStyles.root?.padding,
|
||||
fontSize: componentStyles.root?.fontSize,
|
||||
});
|
||||
expect(actionsElement).toHaveStyle({ margin: contextStyles.actions?.margin });
|
||||
});
|
||||
|
||||
it('should support semantic className and style on actions', () => {
|
||||
render(
|
||||
<Typography.Paragraph
|
||||
classNames={{ actions: 'custom-actions' }}
|
||||
styles={{ actions: { backgroundColor: '#f0f0f0' } }}
|
||||
copyable
|
||||
>
|
||||
Test Typography with Action
|
||||
</Typography.Paragraph>,
|
||||
);
|
||||
|
||||
const rootElement = document.querySelector<HTMLElement>('.ant-typography');
|
||||
expect(rootElement).toBeInTheDocument();
|
||||
|
||||
const actionsElement = document.querySelector<HTMLElement>('.ant-typography-actions');
|
||||
expect(actionsElement).toHaveClass('custom-actions');
|
||||
expect(actionsElement).toHaveStyle({ backgroundColor: 'rgb(240, 240, 240)' });
|
||||
});
|
||||
});
|
||||
39
components/typography/demo/_semantic.tsx
Normal file
39
components/typography/demo/_semantic.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
import useLocale from '../../../.dumi/hooks/useLocale';
|
||||
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
|
||||
|
||||
const locales = {
|
||||
cn: {
|
||||
root: '根元素,包含排版基础样式、布局和定位',
|
||||
actions: '操作区域元素,包含复制、编辑、展开/收起等操作按钮的布局和间距样式',
|
||||
},
|
||||
en: {
|
||||
root: 'Root element with base typography styles, layout, and positioning',
|
||||
actions:
|
||||
'Actions element with layout and spacing styles for copy, edit, expand/collapse buttons',
|
||||
},
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [locale] = useLocale(locales);
|
||||
return (
|
||||
<SemanticPreview
|
||||
componentName="Typography"
|
||||
semantics={[
|
||||
{ name: 'root', desc: locale.root, version: '6.0.0' },
|
||||
{ name: 'actions', desc: locale.actions, version: '6.0.0' },
|
||||
]}
|
||||
>
|
||||
<Typography.Paragraph copyable editable ellipsis={{ rows: 2, expandable: true }}>
|
||||
Ant Design is a design language for background applications, refined by Ant UED Team. It
|
||||
aims to uniform the user interface specs for internal background projects, lower the
|
||||
unnecessary cost of design differences and implementation and liberate the resources of
|
||||
design and front-end development.
|
||||
</Typography.Paragraph>
|
||||
</SemanticPreview>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
60
components/typography/hooks/useTypographySemantic.ts
Normal file
60
components/typography/hooks/useTypographySemantic.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { DirectionType } from '../../config-provider';
|
||||
import { useComponentConfig } from '../../config-provider/context';
|
||||
import { useMergeSemantic } from '../../_util/hooks';
|
||||
import type { BaseTypographyProps, TypographyClassNamesType, TypographyStylesType } from '../Base';
|
||||
|
||||
interface UseTypographySemanticResult {
|
||||
mergedClassNames: ReturnType<
|
||||
typeof useMergeSemantic<TypographyClassNamesType, TypographyStylesType, BaseTypographyProps>
|
||||
>[0];
|
||||
mergedStyles: ReturnType<
|
||||
typeof useMergeSemantic<TypographyClassNamesType, TypographyStylesType, BaseTypographyProps>
|
||||
>[1];
|
||||
prefixCls: string;
|
||||
direction: DirectionType | undefined;
|
||||
contextClassName: string | undefined;
|
||||
contextStyle: React.CSSProperties | undefined;
|
||||
}
|
||||
|
||||
export const useTypographySemantic = (
|
||||
customizePrefixCls?: string,
|
||||
classNames?: TypographyClassNamesType | undefined,
|
||||
styles?: TypographyStylesType | undefined,
|
||||
typographyDirection?: DirectionType,
|
||||
props?: any,
|
||||
): UseTypographySemanticResult => {
|
||||
const {
|
||||
getPrefixCls,
|
||||
direction: contextDirection,
|
||||
className: contextClassName,
|
||||
style: contextStyle,
|
||||
classNames: contextClassNames,
|
||||
styles: contextStyles,
|
||||
} = useComponentConfig('typography');
|
||||
|
||||
const direction = typographyDirection ?? contextDirection;
|
||||
const prefixCls = getPrefixCls('typography', customizePrefixCls);
|
||||
|
||||
const mergedProps: BaseTypographyProps = {
|
||||
...props,
|
||||
prefixCls,
|
||||
direction,
|
||||
};
|
||||
|
||||
const [mergedClassNames, mergedStyles] = useMergeSemantic<
|
||||
TypographyClassNamesType,
|
||||
TypographyStylesType,
|
||||
BaseTypographyProps
|
||||
>([contextClassNames, classNames], [contextStyles, styles], {
|
||||
props: mergedProps,
|
||||
});
|
||||
|
||||
return {
|
||||
mergedClassNames,
|
||||
mergedStyles,
|
||||
prefixCls,
|
||||
direction,
|
||||
contextClassName,
|
||||
contextStyle,
|
||||
};
|
||||
};
|
||||
@@ -4,14 +4,16 @@ import Text from './Text';
|
||||
import Title from './Title';
|
||||
import OriginTypography from './Typography';
|
||||
|
||||
export type TypographyProps = typeof OriginTypography & {
|
||||
export type { TypographyProps } from './Typography';
|
||||
|
||||
type CompoundedComponent = typeof OriginTypography & {
|
||||
Text: typeof Text;
|
||||
Link: typeof Link;
|
||||
Title: typeof Title;
|
||||
Paragraph: typeof Paragraph;
|
||||
};
|
||||
|
||||
const Typography = OriginTypography as TypographyProps;
|
||||
const Typography = OriginTypography as CompoundedComponent;
|
||||
Typography.Text = Text;
|
||||
Typography.Link = Link;
|
||||
Typography.Title = Title;
|
||||
|
||||
Reference in New Issue
Block a user