mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-13 12:59:19 +08:00
Compare commits
10 Commits
demo-debug
...
typography
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38f32d5cf7 | ||
|
|
065ff3d2ad | ||
|
|
77fecc07ca | ||
|
|
08a0e26973 | ||
|
|
0088e0221c | ||
|
|
88bd8f6de8 | ||
|
|
5d3c93bdd6 | ||
|
|
61729477bb | ||
|
|
fdb0d6ca6e | ||
|
|
e93868198b |
@@ -21,7 +21,7 @@ export interface AppProps<P = AnyObject> extends AppConfig {
|
||||
component?: CustomComponent<P> | false;
|
||||
}
|
||||
|
||||
const App: React.FC<AppProps> = (props) => {
|
||||
const App = React.forwardRef<HTMLElement, AppProps>((props, ref) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
children,
|
||||
@@ -79,6 +79,12 @@ const App: React.FC<AppProps> = (props) => {
|
||||
'When using cssVar, ensure `component` is assigned a valid React component string.',
|
||||
);
|
||||
|
||||
devUseWarning('App')(
|
||||
!ref || component !== false,
|
||||
'usage',
|
||||
'`ref` is not supported when `component` is `false`. Please provide a valid `component` instead.',
|
||||
);
|
||||
|
||||
// ============================ Render ============================
|
||||
const Component = component === false ? React.Fragment : component;
|
||||
|
||||
@@ -90,7 +96,7 @@ const App: React.FC<AppProps> = (props) => {
|
||||
return (
|
||||
<AppContext.Provider value={memoizedContextValue}>
|
||||
<AppConfigContext.Provider value={mergedAppConfig}>
|
||||
<Component {...(component === false ? undefined : rootProps)}>
|
||||
<Component {...(component === false ? undefined : { ...rootProps, ref })}>
|
||||
{ModalContextHolder}
|
||||
{messageContextHolder}
|
||||
{notificationContextHolder}
|
||||
@@ -99,7 +105,7 @@ const App: React.FC<AppProps> = (props) => {
|
||||
</AppConfigContext.Provider>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
App.displayName = 'App';
|
||||
|
||||
@@ -247,5 +247,20 @@ describe('App', () => {
|
||||
'Warning: [antd: App] When using cssVar, ensure `component` is assigned a valid React component string.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if component is false and ref is not empty', () => {
|
||||
const domRef = React.createRef<HTMLSpanElement>();
|
||||
render(<App ref={domRef} component={false} />);
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: App] `ref` is not supported when `component` is `false`. Please provide a valid `component` instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it('App should support Ref', () => {
|
||||
const domRef = React.createRef<HTMLSpanElement>();
|
||||
const { container } = render(<App ref={domRef} className="bamboo" component="span" />);
|
||||
expect(domRef.current).toBe(container.querySelector('.bamboo'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
|
||||
import ConfigProvider from '..';
|
||||
import { Button, InputNumber, Select } from '../..';
|
||||
import { Button, InputNumber, Select, Space } from '../..';
|
||||
import { render, waitFakeTimer } from '../../../tests/utils';
|
||||
import theme from '../../theme';
|
||||
import type { GlobalToken } from '../../theme/internal';
|
||||
@@ -113,6 +113,21 @@ describe('ConfigProvider.Theme', () => {
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support Addon component token', () => {
|
||||
const { container } = render(
|
||||
<ConfigProvider theme={{ components: { Addon: { colorText: '#0000FF', algorithm: true } } }}>
|
||||
<Space.Compact>
|
||||
<Space.Addon className="test-addon">Addon Content</Space.Addon>
|
||||
</Space.Compact>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
const addon = container.querySelector('.test-addon')!;
|
||||
expect(addon).toHaveStyle({
|
||||
'--ant-color-text': '#0000FF',
|
||||
});
|
||||
});
|
||||
|
||||
it('hashed should be true if not changed', () => {
|
||||
let hashId = 'hashId';
|
||||
|
||||
|
||||
@@ -121,10 +121,6 @@ const genSingleStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
[`&-active:not(${selectItemCls}-option-disabled)`]: {
|
||||
backgroundColor: token.optionActiveBg,
|
||||
},
|
||||
|
||||
[`&-selected:not(${selectItemCls}-option-disabled)`]: {
|
||||
color: token.optionSelectedColor,
|
||||
fontWeight: token.optionSelectedFontWeight,
|
||||
@@ -135,6 +131,10 @@ const genSingleStyle: GenerateStyle<SelectToken> = (token) => {
|
||||
},
|
||||
},
|
||||
|
||||
[`&-active:not(${selectItemCls}-option-disabled)`]: {
|
||||
backgroundColor: token.optionActiveBg,
|
||||
},
|
||||
|
||||
'&-disabled': {
|
||||
[`&${selectItemCls}-option-selected`]: {
|
||||
backgroundColor: token.colorBgContainerDisabled,
|
||||
|
||||
@@ -16066,6 +16066,28 @@ exports[`renders components/space/demo/compact-nested.tsx extend context correct
|
||||
|
||||
exports[`renders components/space/demo/compact-nested.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/space/demo/component-token.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space-compact"
|
||||
>
|
||||
<div
|
||||
class="ant-space-addon ant-space-addon-compact-item ant-space-addon-compact-first-item css-var-test-id ant-space-addon-variant-outlined"
|
||||
>
|
||||
Addon
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-compact-item ant-btn-compact-last-item"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Button
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/space/demo/component-token.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/space/demo/debug.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
|
||||
@@ -4097,6 +4097,26 @@ exports[`renders components/space/demo/compact-nested.tsx correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/space/demo/component-token.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space-compact"
|
||||
>
|
||||
<div
|
||||
class="ant-space-addon ant-space-addon-compact-item ant-space-addon-compact-first-item css-var-test-id ant-space-addon-variant-outlined"
|
||||
>
|
||||
Addon
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-compact-item ant-btn-compact-last-item"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Button
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/space/demo/debug.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"
|
||||
|
||||
7
components/space/demo/component-token.md
Normal file
7
components/space/demo/component-token.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
使用 `ConfigProvider` 自定义 `Space.Addon` 的主题样式。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `ConfigProvider` to customize the theme of `Space.Addon`.
|
||||
19
components/space/demo/component-token.tsx
Normal file
19
components/space/demo/component-token.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Button, ConfigProvider, Space } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Addon: { colorText: 'blue', algorithm: true },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Space.Compact>
|
||||
<Space.Addon>Addon</Space.Addon>
|
||||
<Button type="primary">Button</Button>
|
||||
</Space.Compact>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
@@ -34,6 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
|
||||
<code src="./demo/debug.tsx" debug>Diverse Child</code>
|
||||
<code src="./demo/gap-in-line.tsx" debug>Flex gap style</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">Custom semantic dom styling</code>
|
||||
<code src="./demo/component-token.tsx" debug>Customize Addon with theme</code>
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
|
||||
<code src="./demo/debug.tsx" debug>多样的 Child</code>
|
||||
<code src="./demo/gap-in-line.tsx" debug>Flex gap 样式</code>
|
||||
<code src="./demo/style-class.tsx" version="6.0.0">自定义语义结构的样式和类</code>
|
||||
<code src="./demo/component-token.tsx" debug>自定义主题</code>
|
||||
|
||||
## API
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resetComponent } from '../../style';
|
||||
import { genCompactItemStyle } from '../../style/compact-item';
|
||||
import { genStyleHooks } from '../../theme/internal';
|
||||
import type { FullToken, GenerateStyle } from '../../theme/internal';
|
||||
@@ -7,11 +8,11 @@ import { genCssVar } from '../../theme/util/genStyleUtils';
|
||||
// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default
|
||||
export interface ComponentToken {}
|
||||
|
||||
interface SpaceToken extends FullToken<'Space'> {
|
||||
interface AddonToken extends FullToken<'Space'> {
|
||||
// Custom token here
|
||||
}
|
||||
|
||||
const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
|
||||
const genSpaceAddonStyle: GenerateStyle<AddonToken> = (token) => {
|
||||
const {
|
||||
componentCls,
|
||||
borderRadius,
|
||||
@@ -27,7 +28,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
|
||||
antCls,
|
||||
} = token;
|
||||
|
||||
const [varName, varRef] = genCssVar(antCls, 'space');
|
||||
const [varName, varRef] = genCssVar(antCls, 'space-addon');
|
||||
|
||||
return {
|
||||
[componentCls]: [
|
||||
@@ -35,6 +36,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
|
||||
// == Base ==
|
||||
// ==========================================================
|
||||
{
|
||||
...resetComponent(token),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 0,
|
||||
@@ -144,7 +146,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
|
||||
};
|
||||
|
||||
// ============================== Export ==============================
|
||||
export default genStyleHooks(['Space', 'Addon'], (token) => [
|
||||
export default genStyleHooks('Addon', (token) => [
|
||||
genSpaceAddonStyle(token),
|
||||
genCompactItemStyle(token, { focus: false }),
|
||||
]);
|
||||
|
||||
@@ -49,6 +49,7 @@ import type { ComponentToken as SelectComponentToken } from '../../select/style'
|
||||
import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style';
|
||||
import type { ComponentToken as SliderComponentToken } from '../../slider/style';
|
||||
import type { ComponentToken as SpaceComponentToken } from '../../space/style';
|
||||
import type { ComponentToken as AddonComponentToken } from '../../space/style/addon';
|
||||
import type { ComponentToken as SpinComponentToken } from '../../spin/style';
|
||||
import type { ComponentToken as SplitterComponentToken } from '../../splitter/style';
|
||||
import type { ComponentToken as StatisticComponentToken } from '../../statistic/style';
|
||||
@@ -68,6 +69,7 @@ import type { ComponentToken as UploadComponentToken } from '../../upload/style'
|
||||
|
||||
export interface ComponentTokenMap {
|
||||
Affix?: AffixComponentToken;
|
||||
Addon?: AddonComponentToken;
|
||||
Alert?: AlertComponentToken;
|
||||
Anchor?: AnchorComponentToken;
|
||||
Avatar?: AvatarComponentToken;
|
||||
|
||||
@@ -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