mirror of
https://github.com/ant-design/ant-design.git
synced 2025-06-07 09:26:06 +08:00
Merge pull request #48735 from ant-design/feature
chore: merge feature to master
This commit is contained in:
commit
2f304d1e27
@ -91,7 +91,7 @@ const ThemeColorPicker: React.FC<ColorPickerProps> = ({ value, onChange, id }) =
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Flex gap="large" align="center" wrap="wrap">
|
||||
<Flex gap="large" align="center" wrap>
|
||||
<Input
|
||||
value={typeof value === 'string' ? value : value?.toHexString()}
|
||||
onChange={(event) => onChange?.(event.target.value)}
|
||||
|
@ -84,7 +84,7 @@ const ThemePicker: React.FC<ThemePickerProps> = (props) => {
|
||||
const { styles } = useStyle();
|
||||
const [locale] = useLocale(locales);
|
||||
return (
|
||||
<Flex gap="large" wrap="wrap">
|
||||
<Flex gap="large" wrap>
|
||||
{(Object.keys(THEMES) as (keyof typeof THEMES)[]).map<React.ReactNode>((theme, index) => (
|
||||
<Flex vertical gap="small" justify="center" align="center" key={theme}>
|
||||
<label
|
||||
|
@ -404,8 +404,7 @@ createRoot(document.getElementById('container')).render(<Demo />);
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Flex wrap="wrap" gap="middle" className="code-box-actions">
|
||||
<Flex wrap gap="middle" className="code-box-actions">
|
||||
{showOnlineUrl && (
|
||||
<Tooltip title={<FormattedMessage id="app.demo.online" />}>
|
||||
<a
|
||||
|
@ -1,13 +1,21 @@
|
||||
import { css, Global } from '@emotion/react';
|
||||
import React from 'react';
|
||||
import { css, Global } from '@emotion/react';
|
||||
import { useTheme } from 'antd-style';
|
||||
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
|
||||
|
||||
export default () => {
|
||||
const { anchorTop } = useTheme();
|
||||
|
||||
React.useInsertionEffect(() => {
|
||||
updateCSS(`@layer global, antd;`, 'site-global', {
|
||||
prepend: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Global
|
||||
styles={css`
|
||||
@layer global {
|
||||
body,
|
||||
div,
|
||||
dl,
|
||||
@ -71,6 +79,7 @@ export default () => {
|
||||
[data-prefers-color='light'] {
|
||||
color-scheme: light;
|
||||
}
|
||||
}
|
||||
`}
|
||||
/>
|
||||
);
|
||||
|
@ -204,6 +204,7 @@ const GlobalLayout: React.FC = () => {
|
||||
<StyleProvider
|
||||
cache={styleCache}
|
||||
linters={[legacyNotSelectorLinter, parentSelectorLinter, NaNLinter]}
|
||||
layer
|
||||
>
|
||||
<SiteContext.Provider value={siteContextValue}>
|
||||
<SiteThemeProvider theme={themeConfig}>
|
||||
|
@ -221,7 +221,8 @@ const RoutesPlugin = (api: IApi) => {
|
||||
const matchRegex = /<style data-type="antd-cssinjs">([\S\s]+?)<\/style>/;
|
||||
const matchList = file.content.match(matchRegex) || [];
|
||||
|
||||
let antdStyle = '';
|
||||
// Init to order the `@layer`
|
||||
let antdStyle = '@layer global, antd;';
|
||||
|
||||
matchList.forEach((text) => {
|
||||
file.content = file.content.replace(text, '');
|
||||
|
@ -7,10 +7,11 @@ import * as React from 'react';
|
||||
import KeyCode from 'rc-util/lib/KeyCode';
|
||||
|
||||
interface TransButtonProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
onClick?: (e?: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onClick?: (e?: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
noStyle?: boolean;
|
||||
autoFocus?: boolean;
|
||||
disabled?: boolean;
|
||||
tabIndex?: number;
|
||||
}
|
||||
|
||||
const inlineStyle: React.CSSProperties = {
|
||||
@ -37,7 +38,7 @@ const TransButton = React.forwardRef<HTMLDivElement, TransButtonProps>((props, r
|
||||
}
|
||||
};
|
||||
|
||||
const { style, noStyle, disabled, ...restProps } = props;
|
||||
const { style, noStyle, disabled, tabIndex = 0, ...restProps } = props;
|
||||
|
||||
let mergedStyle: React.CSSProperties = {};
|
||||
|
||||
@ -59,7 +60,7 @@ const TransButton = React.forwardRef<HTMLDivElement, TransButtonProps>((props, r
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
tabIndex={tabIndex}
|
||||
ref={ref}
|
||||
{...restProps}
|
||||
onKeyDown={onKeyDown}
|
||||
|
@ -15,6 +15,10 @@ import { devUseWarning } from '../_util/warning';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import useStyle from './style';
|
||||
|
||||
export interface AlertRef {
|
||||
nativeElement: HTMLDivElement;
|
||||
}
|
||||
|
||||
export interface AlertProps {
|
||||
/** Type of Alert styles, options:`success`, `info`, `warning`, `error` */
|
||||
type?: 'success' | 'info' | 'warning' | 'error';
|
||||
@ -48,6 +52,8 @@ export interface AlertProps {
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const iconMapFilled = {
|
||||
@ -102,7 +108,7 @@ const CloseIconNode: React.FC<CloseIconProps> = (props) => {
|
||||
) : null;
|
||||
};
|
||||
|
||||
const Alert: React.FC<AlertProps> = (props) => {
|
||||
const Alert = React.forwardRef<AlertRef, AlertProps>((props, ref) => {
|
||||
const {
|
||||
description,
|
||||
prefixCls: customizePrefixCls,
|
||||
@ -120,6 +126,7 @@ const Alert: React.FC<AlertProps> = (props) => {
|
||||
closeText,
|
||||
closeIcon,
|
||||
action,
|
||||
id,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
@ -130,7 +137,12 @@ const Alert: React.FC<AlertProps> = (props) => {
|
||||
warning.deprecated(!closeText, 'closeText', 'closable.closeIcon');
|
||||
}
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const internalRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
nativeElement: internalRef.current!,
|
||||
}));
|
||||
|
||||
const { getPrefixCls, direction, alert } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('alert', customizePrefixCls);
|
||||
|
||||
@ -224,7 +236,8 @@ const Alert: React.FC<AlertProps> = (props) => {
|
||||
>
|
||||
{({ className: motionClassName, style: motionStyle }) => (
|
||||
<div
|
||||
ref={ref}
|
||||
id={id}
|
||||
ref={internalRef}
|
||||
data-show={!closed}
|
||||
className={classNames(alertCls, motionClassName)}
|
||||
style={{ ...alert?.style, ...style, ...motionStyle }}
|
||||
@ -258,7 +271,7 @@ const Alert: React.FC<AlertProps> = (props) => {
|
||||
)}
|
||||
</CSSMotion>,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Alert.displayName = 'Alert';
|
||||
|
@ -6,6 +6,7 @@ interface ErrorBoundaryProps {
|
||||
message?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryStates {
|
||||
@ -28,7 +29,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, description, children } = this.props;
|
||||
const { message, description, id, children } = this.props;
|
||||
const { error, info } = this.state;
|
||||
const componentStack = info && info.componentStack ? info.componentStack : null;
|
||||
const errorMessage = typeof message === 'undefined' ? (error || '').toString() : message;
|
||||
@ -36,6 +37,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
|
||||
if (error) {
|
||||
return (
|
||||
<Alert
|
||||
id={id}
|
||||
type="error"
|
||||
message={errorMessage}
|
||||
description={
|
||||
|
@ -9,6 +9,7 @@ import { act, render, screen, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Popconfirm from '../../popconfirm';
|
||||
import Tooltip from '../../tooltip';
|
||||
import type { AlertRef } from '../Alert';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
|
||||
@ -192,4 +193,13 @@ describe('Alert', () => {
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should support id and ref', () => {
|
||||
const alertRef = React.createRef<AlertRef>();
|
||||
const { container } = render(<Alert id="test-id" ref={alertRef} />);
|
||||
const element = container.querySelector<HTMLDivElement>('#test-id');
|
||||
expect(element).toBeTruthy();
|
||||
expect(alertRef.current?.nativeElement).toBeTruthy();
|
||||
expect(alertRef.current?.nativeElement).toBe(element);
|
||||
});
|
||||
});
|
||||
|
@ -1,12 +1,9 @@
|
||||
import type React from 'react';
|
||||
|
||||
import type { AlertProps } from './Alert';
|
||||
import InternalAlert from './Alert';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
export type { AlertProps } from './Alert';
|
||||
|
||||
type CompoundedComponent = React.FC<AlertProps> & {
|
||||
type CompoundedComponent = typeof InternalAlert & {
|
||||
ErrorBoundary: typeof ErrorBoundary;
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@ import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import classNames from 'classnames';
|
||||
import CSSMotion from 'rc-motion';
|
||||
|
||||
import type { ButtonProps } from './button';
|
||||
import IconWrapper from './IconWrapper';
|
||||
|
||||
type InnerLoadingIconProps = {
|
||||
@ -10,27 +11,29 @@ type InnerLoadingIconProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
iconClassName?: string;
|
||||
};
|
||||
} & Pick<ButtonProps, 'iconPosition'>;
|
||||
|
||||
const InnerLoadingIcon = forwardRef<HTMLSpanElement, InnerLoadingIconProps>(
|
||||
({ prefixCls, className, style, iconClassName }, ref) => {
|
||||
const mergedIconCls = classNames(`${prefixCls}-loading-icon`, className);
|
||||
const InnerLoadingIcon = forwardRef<HTMLSpanElement, InnerLoadingIconProps>((props, ref) => {
|
||||
const { prefixCls, className, style, iconClassName, iconPosition = 'start' } = props;
|
||||
const mergedIconCls = classNames(className, {
|
||||
[`${prefixCls}-loading-icon-end`]: iconPosition === 'end',
|
||||
[`${prefixCls}-loading-icon`]: iconPosition === 'start',
|
||||
});
|
||||
|
||||
return (
|
||||
<IconWrapper prefixCls={prefixCls} className={mergedIconCls} style={style} ref={ref}>
|
||||
<LoadingOutlined className={iconClassName} />
|
||||
</IconWrapper>
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
export interface LoadingIconProps {
|
||||
export type LoadingIconProps = {
|
||||
prefixCls: string;
|
||||
existIcon: boolean;
|
||||
loading?: boolean | object;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
} & Pick<ButtonProps, 'iconPosition'>;
|
||||
|
||||
const getCollapsedWidth = (): React.CSSProperties => ({
|
||||
width: 0,
|
||||
@ -45,11 +48,18 @@ const getRealWidth = (node: HTMLElement): React.CSSProperties => ({
|
||||
});
|
||||
|
||||
const LoadingIcon: React.FC<LoadingIconProps> = (props) => {
|
||||
const { prefixCls, loading, existIcon, className, style } = props;
|
||||
const { prefixCls, loading, existIcon, className, style, iconPosition } = props;
|
||||
const visible = !!loading;
|
||||
|
||||
if (existIcon) {
|
||||
return <InnerLoadingIcon prefixCls={prefixCls} className={className} style={style} />;
|
||||
return (
|
||||
<InnerLoadingIcon
|
||||
prefixCls={prefixCls}
|
||||
className={className}
|
||||
style={style}
|
||||
iconPosition={iconPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -73,6 +83,7 @@ const LoadingIcon: React.FC<LoadingIconProps> = (props) => {
|
||||
style={{ ...style, ...motionStyle }}
|
||||
ref={ref}
|
||||
iconClassName={motionCls}
|
||||
iconPosition={iconPosition}
|
||||
/>
|
||||
)}
|
||||
</CSSMotion>
|
||||
|
@ -1520,6 +1520,467 @@ exports[`renders components/button/demo/icon.tsx extend context correctly 1`] =
|
||||
|
||||
exports[`renders components/button/demo/icon.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/button/demo/icon-position.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="start"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
start
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="end"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
end
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left ant-divider-plain"
|
||||
role="separator"
|
||||
>
|
||||
<span
|
||||
class="ant-divider-inner-text"
|
||||
>
|
||||
Preview
|
||||
</span>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
A
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn ant-btn-text"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-dashed ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
class="ant-tooltip ant-zoom-big-fast-appear ant-zoom-big-fast-appear-prepare ant-zoom-big-fast ant-tooltip-placement-top"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-arrow"
|
||||
style="position: absolute; bottom: 0px; left: 0px;"
|
||||
/>
|
||||
<div
|
||||
class="ant-tooltip-content"
|
||||
>
|
||||
<div
|
||||
class="ant-tooltip-inner"
|
||||
role="tooltip"
|
||||
>
|
||||
search
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="ant-btn ant-btn-dashed"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<a
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
href="https://www.google.com"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-loading"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-btn-loading-icon"
|
||||
style="width: 0px; opacity: 0; transform: scale(0);"
|
||||
>
|
||||
<span
|
||||
aria-label="loading"
|
||||
class="anticon anticon-loading anticon-spin ant-btn-loading-icon-motion-appear ant-btn-loading-icon-motion-appear-start ant-btn-loading-icon-motion"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="loading"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Loading
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/icon-position.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/button/demo/legacy-group.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
@ -2315,6 +2776,31 @@ exports[`renders components/button/demo/multiple.tsx extend context correctly 1`
|
||||
|
||||
exports[`renders components/button/demo/multiple.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/button/demo/noSpace.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
确定
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
确 定
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/noSpace.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/button/demo/size.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
@ -1345,6 +1345,388 @@ exports[`renders components/button/demo/icon.tsx correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/icon-position.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-radio-group ant-radio-group-outline"
|
||||
>
|
||||
<label
|
||||
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button ant-radio-button-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="start"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
start
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="ant-radio-button-wrapper"
|
||||
>
|
||||
<span
|
||||
class="ant-radio-button"
|
||||
>
|
||||
<input
|
||||
class="ant-radio-button-input"
|
||||
type="radio"
|
||||
value="end"
|
||||
/>
|
||||
<span
|
||||
class="ant-radio-button-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
end
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal ant-divider-with-text ant-divider-with-text-left ant-divider-plain"
|
||||
role="separator"
|
||||
>
|
||||
<span
|
||||
class="ant-divider-inner-text"
|
||||
>
|
||||
Preview
|
||||
</span>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-flex ant-flex-align-stretch ant-flex-gap-small ant-flex-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
A
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-default ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-text"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-circle ant-btn-dashed ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-dashed"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Search
|
||||
</span>
|
||||
</button>
|
||||
<a
|
||||
class="ant-btn ant-btn-default ant-btn-icon-only"
|
||||
href="https://www.google.com"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="search"
|
||||
class="anticon anticon-search"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="search"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-loading"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-icon ant-btn-loading-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="loading"
|
||||
class="anticon anticon-loading anticon-spin"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="loading"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
Loading
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/legacy-group.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
@ -1861,6 +2243,29 @@ exports[`renders components/button/demo/multiple.tsx correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/noSpace.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-flex ant-flex-wrap-wrap ant-flex-gap-middle"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
确定
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
确 定
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/button/demo/size.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
@ -441,4 +441,10 @@ describe('Button', () => {
|
||||
const { container } = render(<Button type={'' as any} />);
|
||||
expect(container.querySelector('.ant-btn-default')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support autoInsertSpace', () => {
|
||||
const text = '确定';
|
||||
const { container } = render(<Button autoInsertSpace={false}>{text}</Button>);
|
||||
expect(container.querySelector<HTMLButtonElement>('button')?.textContent).toBe(text);
|
||||
});
|
||||
});
|
||||
|
@ -24,6 +24,7 @@ export type LegacyButtonType = ButtonType | 'danger';
|
||||
export interface BaseButtonProps {
|
||||
type?: ButtonType;
|
||||
icon?: React.ReactNode;
|
||||
iconPosition?: 'start' | 'end';
|
||||
shape?: ButtonShape;
|
||||
size?: SizeType;
|
||||
disabled?: boolean;
|
||||
@ -50,6 +51,7 @@ type MergedHTMLAttributes = Omit<
|
||||
export interface ButtonProps extends BaseButtonProps, MergedHTMLAttributes {
|
||||
href?: string;
|
||||
htmlType?: ButtonHTMLType;
|
||||
autoInsertSpace?: boolean;
|
||||
}
|
||||
|
||||
type LoadingConfigType = {
|
||||
@ -90,12 +92,14 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
rootClassName,
|
||||
children,
|
||||
icon,
|
||||
iconPosition = 'start',
|
||||
ghost = false,
|
||||
block = false,
|
||||
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
|
||||
htmlType = 'button',
|
||||
classNames: customClassNames,
|
||||
style: customStyle = {},
|
||||
autoInsertSpace,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@ -103,7 +107,10 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
// Compatible with original `type` behavior
|
||||
const mergedType = type || 'default';
|
||||
|
||||
const { getPrefixCls, autoInsertSpaceInButton, direction, button } = useContext(ConfigContext);
|
||||
const { getPrefixCls, direction, button } = useContext(ConfigContext);
|
||||
|
||||
const mergedInsertSpace = autoInsertSpace ?? button?.autoInsertSpace ?? true;
|
||||
|
||||
const prefixCls = getPrefixCls('btn', customizePrefixCls);
|
||||
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
@ -149,7 +156,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
useEffect(() => {
|
||||
// FIXME: for HOC usage like <FormatMessage />
|
||||
if (!buttonRef || !(buttonRef as any).current || autoInsertSpaceInButton === false) {
|
||||
if (!buttonRef || !(buttonRef as any).current || !mergedInsertSpace) {
|
||||
return;
|
||||
}
|
||||
const buttonText = (buttonRef as any).current.textContent;
|
||||
@ -188,7 +195,6 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
);
|
||||
}
|
||||
|
||||
const autoInsertSpace = autoInsertSpaceInButton !== false;
|
||||
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
|
||||
|
||||
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
|
||||
@ -212,7 +218,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
[`${prefixCls}-icon-only`]: !children && children !== 0 && !!iconType,
|
||||
[`${prefixCls}-background-ghost`]: ghost && !isUnBorderedButtonType(mergedType),
|
||||
[`${prefixCls}-loading`]: innerLoading,
|
||||
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace && !innerLoading,
|
||||
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && mergedInsertSpace && !innerLoading,
|
||||
[`${prefixCls}-block`]: block,
|
||||
[`${prefixCls}-dangerous`]: !!danger,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
@ -225,7 +231,11 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
|
||||
const fullStyle: React.CSSProperties = { ...button?.style, ...customStyle };
|
||||
|
||||
const iconClasses = classNames(customClassNames?.icon, button?.classNames?.icon);
|
||||
const isIconPositionEnd = iconPosition === 'end' && children && children !== 0 && iconType;
|
||||
|
||||
const iconClasses = classNames(customClassNames?.icon, button?.classNames?.icon, {
|
||||
[`${prefixCls}-icon-end`]: isIconPositionEnd,
|
||||
});
|
||||
const iconStyle: React.CSSProperties = {
|
||||
...(styles?.icon || {}),
|
||||
...(button?.styles?.icon || {}),
|
||||
@ -237,11 +247,27 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
{icon}
|
||||
</IconWrapper>
|
||||
) : (
|
||||
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={!!innerLoading} />
|
||||
<LoadingIcon
|
||||
existIcon={!!icon}
|
||||
prefixCls={prefixCls}
|
||||
loading={!!innerLoading}
|
||||
iconPosition={iconPosition}
|
||||
/>
|
||||
);
|
||||
|
||||
const kids =
|
||||
children || children === 0 ? spaceChildren(children, needInserted && autoInsertSpace) : null;
|
||||
children || children === 0 ? spaceChildren(children, needInserted && mergedInsertSpace) : null;
|
||||
|
||||
const genButtonContent = (iconComponent: React.ReactNode, kidsComponent: React.ReactNode) => {
|
||||
const isRTL = direction === 'rtl';
|
||||
const iconFirst = (iconPosition === 'start' && !isRTL) || (iconPosition === 'end' && isRTL);
|
||||
return (
|
||||
<>
|
||||
{iconFirst ? iconComponent : kidsComponent}
|
||||
{iconFirst ? kidsComponent : iconComponent}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return wrapCSSVar(
|
||||
@ -256,8 +282,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
ref={buttonRef as React.Ref<HTMLAnchorElement>}
|
||||
tabIndex={mergedDisabled ? -1 : 0}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
{genButtonContent(iconNode, kids)}
|
||||
</a>,
|
||||
);
|
||||
}
|
||||
@ -272,8 +297,7 @@ const InternalCompoundedButton = React.forwardRef<
|
||||
disabled={mergedDisabled}
|
||||
ref={buttonRef as React.Ref<HTMLButtonElement>}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
{genButtonContent(iconNode, kids)}
|
||||
|
||||
{/* Styles: compact */}
|
||||
{!!compactItemClassnames && <CompactCmp key="compact" prefixCls={prefixCls} />}
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Button, Flex } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Button type="primary">Primary Button</Button>
|
||||
<Button>Default Button</Button>
|
||||
<Button type="dashed">Dashed Button</Button>
|
||||
|
@ -7,7 +7,7 @@ const Text2 = () => <span>部署</span>;
|
||||
const Text3 = () => <>Submit</>;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Button>
|
||||
<span>
|
||||
<span>部署</span>
|
||||
|
@ -24,12 +24,12 @@ const App: React.FC = () => (
|
||||
}}
|
||||
>
|
||||
<Flex gap="small" vertical>
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Button type="text">TEXT</Button>
|
||||
<Button type="primary">CONTAINED</Button>
|
||||
<Button>OUTLINED</Button>
|
||||
</Flex>
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Button type="text" disabled>
|
||||
TEXT
|
||||
</Button>
|
||||
@ -38,7 +38,7 @@ const App: React.FC = () => (
|
||||
</Button>
|
||||
<Button disabled>OUTLINED</Button>
|
||||
</Flex>
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Button type="text" size="small">
|
||||
TEXT
|
||||
</Button>
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Button, Flex } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Button type="primary" danger>
|
||||
Primary
|
||||
</Button>
|
||||
|
@ -19,7 +19,7 @@ const App: React.FC = () => {
|
||||
</Divider>
|
||||
<ConfigProvider componentSize={size}>
|
||||
<Flex gap="small" vertical>
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Tooltip title="search">
|
||||
<Button type="primary" shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
@ -34,7 +34,7 @@ const App: React.FC = () => {
|
||||
</Tooltip>
|
||||
<Button icon={<SearchOutlined />}>Search</Button>
|
||||
</Flex>
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Tooltip title="search">
|
||||
<Button shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Button, Flex } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex wrap="wrap" gap="small" className="site-button-ghost-wrapper">
|
||||
<Flex wrap gap="small" className="site-button-ghost-wrapper">
|
||||
<Button type="primary" ghost>
|
||||
Primary
|
||||
</Button>
|
||||
|
7
components/button/demo/icon-position.md
Normal file
7
components/button/demo/icon-position.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
通过设置 `iconPosition` 为 `start` 或 `end` 分别设置按钮图标的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
Set the position of the button icon by setting `iconPosition` to `start` or `end` respectively.
|
60
components/button/demo/icon-position.tsx
Normal file
60
components/button/demo/icon-position.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Flex, Radio, Space, Tooltip } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [position, setPosition] = useState<'start' | 'end'>('start');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space>
|
||||
<Radio.Group value={position} onChange={(e) => setPosition(e.target.value)}>
|
||||
<Radio.Button value="start">start</Radio.Button>
|
||||
<Radio.Button value="end">end</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
<Divider orientation="left" plain>
|
||||
Preview
|
||||
</Divider>
|
||||
<Flex gap="small" vertical>
|
||||
<Flex wrap gap="small">
|
||||
<Tooltip title="search">
|
||||
<Button type="primary" shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
<Button type="primary" shape="circle">
|
||||
A
|
||||
</Button>
|
||||
<Button type="primary" icon={<SearchOutlined />} iconPosition={position}>
|
||||
Search
|
||||
</Button>
|
||||
<Tooltip title="search">
|
||||
<Button shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
<Button icon={<SearchOutlined />} iconPosition={position}>
|
||||
Search
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex wrap gap="small">
|
||||
<Tooltip title="search">
|
||||
<Button shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
<Button icon={<SearchOutlined />} type="text" iconPosition={position}>
|
||||
Search
|
||||
</Button>
|
||||
<Tooltip title="search">
|
||||
<Button type="dashed" shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
<Button type="dashed" icon={<SearchOutlined />} iconPosition={position}>
|
||||
Search
|
||||
</Button>
|
||||
<Button icon={<SearchOutlined />} href="https://www.google.com" iconPosition={position} />
|
||||
<Button type="primary" loading iconPosition={position}>
|
||||
Loading
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,11 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
当需要在 `Button` 内嵌入 `Icon` 时,可以设置 `icon` 属性,或者直接在 `Button` 内使用 `Icon` 组件。
|
||||
|
||||
如果想控制 `Icon` 具体的位置,只能直接使用 `Icon` 组件,而非 `icon` 属性。
|
||||
可以通过 `icon `属性添加图标,并使用 `iconPosition` 调整图标的位置。
|
||||
|
||||
## en-US
|
||||
|
||||
`Button` components can contain an `Icon`. This is done by setting the `icon` property or placing an `Icon` component within the `Button`.
|
||||
|
||||
If you want specific control over the positioning and placement of the `Icon`, then that should be done by placing the `Icon` component within the `Button` rather than using the `icon` property.
|
||||
You can add an icon through the `icon` property and adjust the position of the icon using `iconPosition`.
|
||||
|
@ -4,7 +4,7 @@ import { Button, Flex, Tooltip } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex gap="small" vertical>
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Tooltip title="search">
|
||||
<Button type="primary" shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
@ -19,7 +19,7 @@ const App: React.FC = () => (
|
||||
</Tooltip>
|
||||
<Button icon={<SearchOutlined />}>Search</Button>
|
||||
</Flex>
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
<Tooltip title="search">
|
||||
<Button shape="circle" icon={<SearchOutlined />} />
|
||||
</Tooltip>
|
||||
|
@ -23,7 +23,7 @@ const App: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Flex gap="small" vertical>
|
||||
<Flex gap="small" align="center" wrap="wrap">
|
||||
<Flex gap="small" align="center" wrap>
|
||||
<Button type="primary" loading>
|
||||
Loading
|
||||
</Button>
|
||||
@ -32,7 +32,7 @@ const App: React.FC = () => {
|
||||
</Button>
|
||||
<Button type="primary" icon={<PoweroffOutlined />} loading />
|
||||
</Flex>
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Button type="primary" loading={loadings[0]} onClick={() => enterLoading(0)}>
|
||||
Click me!
|
||||
</Button>
|
||||
|
7
components/button/demo/noSpace.md
Normal file
7
components/button/demo/noSpace.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
我们默认在两个汉字之间添加空格,可以通过设置 `autoInsertSpace` 为 `false` 关闭。
|
||||
|
||||
## en-US
|
||||
|
||||
We add a space between two Chinese characters by default, which can be removed by setting `autoInsertSpace` to `false`.
|
15
components/button/demo/noSpace.tsx
Normal file
15
components/button/demo/noSpace.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Button, Flex } from 'antd';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Flex gap="middle" wrap>
|
||||
<Button type="primary" autoInsertSpace={false}>
|
||||
确定
|
||||
</Button>
|
||||
<Button type="primary" autoInsertSpace>
|
||||
确定
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default App;
|
@ -18,7 +18,7 @@ const App: React.FC = () => {
|
||||
Preview
|
||||
</Divider>
|
||||
<Flex gap="small" align="flex-start" vertical>
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Button type="primary" size={size}>
|
||||
Primary
|
||||
</Button>
|
||||
@ -30,7 +30,7 @@ const App: React.FC = () => {
|
||||
<Button type="link" size={size}>
|
||||
Link
|
||||
</Button>
|
||||
<Flex gap="small" wrap="wrap">
|
||||
<Flex gap="small" wrap>
|
||||
<Button type="primary" icon={<DownloadOutlined />} size={size} />
|
||||
<Button type="primary" shape="circle" icon={<DownloadOutlined />} size={size} />
|
||||
<Button type="primary" shape="round" icon={<DownloadOutlined />} size={size} />
|
||||
|
@ -35,6 +35,7 @@ And 4 other properties additionally.
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">Type</code>
|
||||
<code src="./demo/icon.tsx">Icon</code>
|
||||
<code src="./demo/icon-position.tsx" version="5.17.0">Icon Position</code>
|
||||
<code src="./demo/debug-icon.tsx" debug>Debug Icon</code>
|
||||
<code src="./demo/debug-block.tsx" debug>Debug Block</code>
|
||||
<code src="./demo/size.tsx">Size</code>
|
||||
@ -57,6 +58,7 @@ Different button styles can be generated by setting Button properties. The recom
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoInsertSpace | We add a space between two Chinese characters by default, which can be removed by setting `autoInsertSpace` to `false`. | boolean | `true` | 5.17.0 |
|
||||
| block | Option to fit button width to its parent width | boolean | false | |
|
||||
| classNames | Semantic DOM class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 |
|
||||
| danger | Set the danger status of button | boolean | false | |
|
||||
@ -65,13 +67,14 @@ Different button styles can be generated by setting Button properties. The recom
|
||||
| href | Redirect url of link button | string | - | |
|
||||
| htmlType | Set the original html `type` of `button`, see: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | |
|
||||
| icon | Set the icon component of button | ReactNode | - | |
|
||||
| iconPosition | Set the icon position of button | `start` \| `end` | `start` | 5.17.0 |
|
||||
| loading | Set the loading status of button | boolean \| { delay: number } | false | |
|
||||
| shape | Can be set button shape | `default` \| `circle` \| `round` | `default` | |
|
||||
| size | Set the size of button | `large` \| `middle` \| `small` | `middle` | |
|
||||
| styles | Semantic DOM style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 |
|
||||
| target | Same as target attribute of a, works when href is specified | string | - | |
|
||||
| type | Set button type | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
|
||||
| onClick | Set the handler to handle `click` event | (event: MouseEvent) => void | - | |
|
||||
| onClick | Set the handler to handle `click` event | (event: React.MouseEvent<HTMLElement, MouseEvent>) => void | - | |
|
||||
|
||||
It accepts all props which native buttons support.
|
||||
|
||||
@ -95,18 +98,6 @@ If you don't need this feature, you can set `disabled` of `wave` in [ConfigProvi
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
### How to remove space between 2 chinese characters?
|
||||
|
||||
Following the Ant Design specification, we will add one space between if Button (exclude Text button and Link button) contains two Chinese characters only. If you don't need that, you can use [ConfigProvider](/components/config-provider/#api) to set `autoInsertSpaceInButton` as `false`.
|
||||
|
||||
```jsx
|
||||
<ConfigProvider autoInsertSpaceInButton={false}>
|
||||
<Button>按钮</Button>
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" width="100px" height="64px" style="box-shadow: none; margin: 0;" alt="Button with two Chinese characters" />
|
||||
|
||||
<style>
|
||||
.site-button-ghost-wrapper {
|
||||
padding: 16px;
|
||||
|
@ -37,7 +37,8 @@ group:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">按钮类型</code>
|
||||
<code src="./demo/icon.tsx">图标按钮</code>
|
||||
<code src="./demo/icon.tsx" >按钮图标</code>
|
||||
<code src="./demo/icon-position.tsx" version="5.17.0">按钮图标位置</code>
|
||||
<code src="./demo/debug-icon.tsx" debug>调试图标按钮</code>
|
||||
<code src="./demo/debug-block.tsx" debug>调试按钮block属性</code>
|
||||
<code src="./demo/size.tsx">按钮尺寸</code>
|
||||
@ -51,6 +52,7 @@ group:
|
||||
<code src="./demo/chinese-chars-loading.tsx" debug>加载中状态 bug 还原</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
<code src="./demo/linear-gradient.tsx">渐变按钮</code>
|
||||
<code src="./demo/noSpace.tsx" version="5.17.0">移除两个汉字之间的空格</code>
|
||||
|
||||
## API
|
||||
|
||||
@ -62,6 +64,7 @@ group:
|
||||
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoInsertSpace | 我们默认提供两个汉字之间的空格,可以设置 `autoInsertSpace` 为 `false` 关闭 | boolean | `true` | 5.17.0 |
|
||||
| block | 将按钮宽度调整为其父宽度的选项 | boolean | false | |
|
||||
| classNames | 语义化结构 class | [Record<SemanticDOM, string>](#semantic-dom) | - | 5.4.0 |
|
||||
| danger | 设置危险按钮 | boolean | false | |
|
||||
@ -70,13 +73,14 @@ group:
|
||||
| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - | |
|
||||
| htmlType | 设置 `button` 原生的 `type` 值,可选值请参考 [HTML 标准](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type) | string | `button` | |
|
||||
| icon | 设置按钮的图标组件 | ReactNode | - | |
|
||||
| iconPosition | 设置按钮图标组件的位置 | `start` \| `end` | `start` | 5.17.0 |
|
||||
| loading | 设置按钮载入状态 | boolean \| { delay: number } | false | |
|
||||
| shape | 设置按钮形状 | `default` \| `circle` \| `round` | `default` | |
|
||||
| size | 设置按钮大小 | `large` \| `middle` \| `small` | `middle` | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.4.0 |
|
||||
| target | 相当于 a 链接的 target 属性,href 存在时生效 | string | - | |
|
||||
| type | 设置按钮类型 | `primary` \| `dashed` \| `link` \| `text` \| `default` | `default` | |
|
||||
| onClick | 点击按钮时的回调 | (event: MouseEvent) => void | - | |
|
||||
| onClick | 点击按钮时的回调 | (event: React.MouseEvent<HTMLElement, MouseEvent>) => void | - | |
|
||||
|
||||
支持原生 button 的其他所有属性。
|
||||
|
||||
@ -100,18 +104,6 @@ group:
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
### 如何移除两个汉字之间的空格?
|
||||
|
||||
根据 Ant Design 设计规范要求,我们会在按钮内(文本按钮和链接按钮除外)只有两个汉字时自动添加空格,如果你不需要这个特性,可以设置 [ConfigProvider](/components/config-provider-cn#api) 的 `autoInsertSpaceInButton` 为 `false`。
|
||||
|
||||
```jsx
|
||||
<ConfigProvider autoInsertSpaceInButton={false}>
|
||||
<Button>按钮</Button>
|
||||
</ConfigProvider>
|
||||
```
|
||||
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" style="box-shadow: none; margin: 0" width="100px" height="64px" alt="移除两个汉字之间的空格" />
|
||||
|
||||
<style>
|
||||
.site-button-ghost-wrapper {
|
||||
padding: 16px;
|
||||
|
@ -13,7 +13,6 @@ export type { ComponentToken };
|
||||
// ============================== Shared ==============================
|
||||
const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSSObject => {
|
||||
const { componentCls, iconCls, fontWeight } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
outline: 'none',
|
||||
@ -41,6 +40,10 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
|
||||
[`${componentCls}-icon`]: {
|
||||
lineHeight: 0,
|
||||
// iconPosition in end
|
||||
[`&-end`]: {
|
||||
marginInlineStart: token.marginXS,
|
||||
},
|
||||
},
|
||||
|
||||
// Leave a space between icon and text.
|
||||
@ -52,6 +55,9 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
|
||||
[`&${componentCls}-loading-icon, &:not(:last-child)`]: {
|
||||
marginInlineEnd: token.marginXS,
|
||||
},
|
||||
[`&${componentCls}-loading-icon-end`]: {
|
||||
marginInlineStart: token.marginXS,
|
||||
},
|
||||
},
|
||||
|
||||
'> a': {
|
||||
|
@ -1,5 +1,316 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders components/carousel/demo/arrows.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-carousel"
|
||||
>
|
||||
<div
|
||||
class="slick-slider slick-initialized"
|
||||
dir="ltr"
|
||||
>
|
||||
<button
|
||||
aria-label="prev"
|
||||
class="slick-arrow slick-prev slick-disabled"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display: block;"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="slick-list"
|
||||
>
|
||||
<div
|
||||
class="slick-track"
|
||||
style="opacity: 1; transform: translate3d(0px, 0px, 0px);"
|
||||
>
|
||||
<div
|
||||
aria-hidden="false"
|
||||
class="slick-slide slick-active slick-current"
|
||||
data-index="0"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="1"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="2"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="3"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="next"
|
||||
class="slick-arrow slick-next"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display: block;"
|
||||
type="button"
|
||||
/>
|
||||
<ul
|
||||
class="slick-dots slick-dots-bottom"
|
||||
style="display: block;"
|
||||
>
|
||||
<li
|
||||
class="slick-active"
|
||||
>
|
||||
<button>
|
||||
1
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
2
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
3
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
4
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-carousel ant-carousel-vertical"
|
||||
>
|
||||
<div
|
||||
class="slick-slider slick-vertical slick-initialized"
|
||||
dir="ltr"
|
||||
>
|
||||
<button
|
||||
aria-label="prev"
|
||||
class="slick-arrow slick-prev slick-disabled"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display: block;"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="slick-list"
|
||||
style="height: 0px;"
|
||||
>
|
||||
<div
|
||||
class="slick-track"
|
||||
style="opacity: 1; transform: translate3d(0px, 0px, 0px);"
|
||||
>
|
||||
<div
|
||||
aria-hidden="false"
|
||||
class="slick-slide slick-active slick-current"
|
||||
data-index="0"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="1"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="2"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="3"
|
||||
style="outline: none; width: 0px;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width: 100%; display: inline-block;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin: 0px; height: 160px; color: rgb(255, 255, 255); line-height: 160px; text-align: center; background: rgb(54, 77, 121);"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="next"
|
||||
class="slick-arrow slick-next"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display: block;"
|
||||
type="button"
|
||||
/>
|
||||
<ul
|
||||
class="slick-dots slick-dots-left"
|
||||
style="display: block;"
|
||||
>
|
||||
<li
|
||||
class="slick-active"
|
||||
>
|
||||
<button>
|
||||
1
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
2
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
3
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
4
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/carousel/demo/arrows.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: React does not recognize the \`%s\` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase \`%s\` instead. If you accidentally passed it from a parent component, remove it from the DOM element.%s",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/carousel/demo/autoplay.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-carousel"
|
||||
|
@ -1,5 +1,309 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders components/carousel/demo/arrows.tsx correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-carousel"
|
||||
>
|
||||
<div
|
||||
class="slick-slider slick-initialized"
|
||||
dir="ltr"
|
||||
>
|
||||
<button
|
||||
aria-label="prev"
|
||||
class="slick-arrow slick-prev slick-disabled"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display:block"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="slick-list"
|
||||
>
|
||||
<div
|
||||
class="slick-track"
|
||||
style="width:400%;left:0%"
|
||||
>
|
||||
<div
|
||||
aria-hidden="false"
|
||||
class="slick-slide slick-active slick-current"
|
||||
data-index="0"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="1"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="2"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="3"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="next"
|
||||
class="slick-arrow slick-next"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display:block"
|
||||
type="button"
|
||||
/>
|
||||
<ul
|
||||
class="slick-dots slick-dots-bottom"
|
||||
style="display:block"
|
||||
>
|
||||
<li
|
||||
class="slick-active"
|
||||
>
|
||||
<button>
|
||||
1
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
2
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
3
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
4
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-carousel ant-carousel-vertical"
|
||||
>
|
||||
<div
|
||||
class="slick-slider slick-vertical slick-initialized"
|
||||
dir="ltr"
|
||||
>
|
||||
<button
|
||||
aria-label="prev"
|
||||
class="slick-arrow slick-prev slick-disabled"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display:block"
|
||||
type="button"
|
||||
/>
|
||||
<div
|
||||
class="slick-list"
|
||||
>
|
||||
<div
|
||||
class="slick-track"
|
||||
style="width:400%;left:0%"
|
||||
>
|
||||
<div
|
||||
aria-hidden="false"
|
||||
class="slick-slide slick-active slick-current"
|
||||
data-index="0"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
1
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="1"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
2
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="2"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
3
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="3"
|
||||
style="outline:none;width:25%"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style="width:100%;display:inline-block"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h3
|
||||
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
|
||||
>
|
||||
4
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="next"
|
||||
class="slick-arrow slick-next"
|
||||
currentslide="0"
|
||||
data-role="none"
|
||||
slidecount="4"
|
||||
style="display:block"
|
||||
type="button"
|
||||
/>
|
||||
<ul
|
||||
class="slick-dots slick-dots-left"
|
||||
style="display:block"
|
||||
>
|
||||
<li
|
||||
class="slick-active"
|
||||
>
|
||||
<button>
|
||||
1
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
2
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
3
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class=""
|
||||
>
|
||||
<button>
|
||||
4
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/carousel/demo/autoplay.tsx correctly 1`] = `
|
||||
<div
|
||||
class="ant-carousel"
|
||||
|
7
components/carousel/demo/arrows.md
Normal file
7
components/carousel/demo/arrows.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
显示切换箭头。
|
||||
|
||||
## en-US
|
||||
|
||||
Show the arrows for switching.
|
47
components/carousel/demo/arrows.tsx
Normal file
47
components/carousel/demo/arrows.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Carousel } from 'antd';
|
||||
|
||||
const contentStyle: React.CSSProperties = {
|
||||
margin: 0,
|
||||
height: '160px',
|
||||
color: '#fff',
|
||||
lineHeight: '160px',
|
||||
textAlign: 'center',
|
||||
background: '#364d79',
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Carousel arrows infinite={false}>
|
||||
<div>
|
||||
<h3 style={contentStyle}>1</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>2</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>3</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>4</h3>
|
||||
</div>
|
||||
</Carousel>
|
||||
<br />
|
||||
<Carousel arrows dotPosition="left" infinite={false}>
|
||||
<div>
|
||||
<h3 style={contentStyle}>1</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>2</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>3</h3>
|
||||
</div>
|
||||
<div>
|
||||
<h3 style={contentStyle}>4</h3>
|
||||
</div>
|
||||
</Carousel>
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
@ -22,6 +22,7 @@ demo:
|
||||
<code src="./demo/position.tsx">Position</code>
|
||||
<code src="./demo/autoplay.tsx">Scroll automatically</code>
|
||||
<code src="./demo/fade.tsx">Fade in</code>
|
||||
<code src="./demo/arrows.tsx" version="5.17.0">Arrows for switching</code>
|
||||
<code src="./demo/component-token.tsx" debug>Component Token</code>
|
||||
|
||||
## API
|
||||
@ -30,6 +31,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| arrows | Whether to show switch arrows | boolean | false | 5.17.0 |
|
||||
| autoplay | Whether to scroll automatically | boolean | false | |
|
||||
| autoplaySpeed | Delay between each auto scroll (in milliseconds) | number | 3000 | |
|
||||
| dotPosition | The position of the dots, which can be one of `top` `bottom` `left` `right` | string | `bottom` | |
|
||||
|
@ -35,6 +35,8 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => {
|
||||
const {
|
||||
dots = true,
|
||||
arrows = false,
|
||||
prevArrow = <button type="button" aria-label="prev" />,
|
||||
nextArrow = <button type="button" aria-label="next" />,
|
||||
draggable = false,
|
||||
waitForAnimate = false,
|
||||
dotPosition = 'bottom',
|
||||
@ -115,6 +117,8 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>((props, ref) => {
|
||||
dots={enableDots}
|
||||
dotsClass={dsClass}
|
||||
arrows={arrows}
|
||||
prevArrow={prevArrow}
|
||||
nextArrow={nextArrow}
|
||||
draggable={draggable}
|
||||
verticalSwiping={vertical}
|
||||
waitForAnimate={waitForAnimate}
|
||||
|
@ -23,6 +23,7 @@ demo:
|
||||
<code src="./demo/position.tsx">位置</code>
|
||||
<code src="./demo/autoplay.tsx">自动切换</code>
|
||||
<code src="./demo/fade.tsx">渐显</code>
|
||||
<code src="./demo/arrows.tsx" version="5.17.0">切换箭头</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
|
||||
## API
|
||||
@ -31,6 +32,7 @@ demo:
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| arrows | 是否显示箭头 | boolean | false | 5.17.0 |
|
||||
| autoplay | 是否自动切换 | boolean | false | |
|
||||
| autoplaySpeed | 自动切换的间隔(毫秒) | number | 3000 | |
|
||||
| dotPosition | 面板指示点位置,可选 `top` `bottom` `left` `right` | string | `bottom` | |
|
||||
|
@ -2,7 +2,7 @@ import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { resetComponent } from '../../style';
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genStyleHooks, mergeToken } from '../../theme/internal';
|
||||
import { genStyleHooks } from '../../theme/internal';
|
||||
|
||||
export interface ComponentToken {
|
||||
/**
|
||||
@ -15,6 +15,16 @@ export interface ComponentToken {
|
||||
* @descEN Height of indicator
|
||||
*/
|
||||
dotHeight: number;
|
||||
/**
|
||||
* @desc 指示点之间的间距
|
||||
* @descEN gap between indicator
|
||||
*/
|
||||
dotGap: number;
|
||||
/**
|
||||
* @desc 指示点距离边缘的距离
|
||||
* @descEN dot offset to Carousel edge
|
||||
*/
|
||||
dotOffset: number;
|
||||
/** @deprecated Use `dotActiveWidth` instead. */
|
||||
dotWidthActive: number;
|
||||
/**
|
||||
@ -22,19 +32,22 @@ export interface ComponentToken {
|
||||
* @descEN Width of active indicator
|
||||
*/
|
||||
dotActiveWidth: number;
|
||||
/**
|
||||
* @desc 切换箭头大小
|
||||
* @descEN Size of arrows
|
||||
*/
|
||||
arrowSize: number;
|
||||
/**
|
||||
* @desc 切换箭头边距
|
||||
* @descEN arrows offset to Carousel edge
|
||||
*/
|
||||
arrowOffset: number;
|
||||
}
|
||||
|
||||
interface CarouselToken extends FullToken<'Carousel'> {
|
||||
carouselArrowSize: string | number;
|
||||
carouselDotOffset: string | number;
|
||||
carouselDotInline: string | number;
|
||||
}
|
||||
interface CarouselToken extends FullToken<'Carousel'> {}
|
||||
|
||||
const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
const { componentCls, antCls, carouselArrowSize, carouselDotOffset, marginXXS } = token;
|
||||
const arrowOffset = token.calc(carouselArrowSize).mul(-1.25).equal();
|
||||
|
||||
const carouselDotMargin = marginXXS;
|
||||
const { componentCls, antCls } = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
@ -133,60 +146,93 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
display: 'block',
|
||||
height: 'auto',
|
||||
},
|
||||
|
||||
'.slick-arrow.slick-hidden': {
|
||||
display: 'none',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const genArrowsStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
const { componentCls, motionDurationSlow, arrowSize, arrowOffset } = token;
|
||||
const arrowLength = token.calc(arrowSize).div(1.414).equal();
|
||||
|
||||
return [
|
||||
{
|
||||
[componentCls]: {
|
||||
// Arrows
|
||||
'.slick-prev, .slick-next': {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
display: 'block',
|
||||
width: carouselArrowSize,
|
||||
height: carouselArrowSize,
|
||||
marginTop: token.calc(carouselArrowSize).mul(-1).div(2).equal(),
|
||||
padding: 0,
|
||||
color: 'transparent',
|
||||
fontSize: 0,
|
||||
lineHeight: 0,
|
||||
width: arrowSize,
|
||||
height: arrowSize,
|
||||
transform: 'translateY(-50%)',
|
||||
color: '#fff',
|
||||
opacity: 0.4,
|
||||
background: 'transparent',
|
||||
padding: 0,
|
||||
lineHeight: 0,
|
||||
border: 0,
|
||||
outline: 'none',
|
||||
cursor: 'pointer',
|
||||
zIndex: 1,
|
||||
transition: `opacity ${motionDurationSlow}`,
|
||||
|
||||
'&:hover, &:focus': {
|
||||
color: 'transparent',
|
||||
background: 'transparent',
|
||||
outline: 'none',
|
||||
|
||||
'&::before': {
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
'&.slick-disabled': {
|
||||
pointerEvents: 'none',
|
||||
opacity: 0,
|
||||
},
|
||||
|
||||
'&.slick-disabled::before': {
|
||||
opacity: 0.25,
|
||||
'&::after': {
|
||||
boxSizing: 'border-box',
|
||||
position: 'absolute',
|
||||
top: token.calc(arrowSize).sub(arrowLength).div(2).equal(),
|
||||
insetInlineStart: token.calc(arrowSize).sub(arrowLength).div(2).equal(),
|
||||
display: 'inline-block',
|
||||
width: arrowLength,
|
||||
height: arrowLength,
|
||||
border: `0 solid currentcolor`,
|
||||
borderInlineWidth: '2px 0',
|
||||
borderBlockWidth: '2px 0',
|
||||
borderRadius: 1,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
|
||||
'.slick-prev': {
|
||||
insetInlineStart: arrowOffset,
|
||||
|
||||
'&::before': {
|
||||
content: '"←"',
|
||||
'&::after': {
|
||||
transform: 'rotate(-45deg)',
|
||||
},
|
||||
},
|
||||
|
||||
'.slick-next': {
|
||||
insetInlineEnd: arrowOffset,
|
||||
|
||||
'&::before': {
|
||||
content: '"→"',
|
||||
'&::after': {
|
||||
transform: 'rotate(135deg)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
// Dots
|
||||
const genDotsStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
const {
|
||||
componentCls,
|
||||
dotOffset,
|
||||
dotWidth,
|
||||
dotHeight,
|
||||
dotGap,
|
||||
colorBgContainer,
|
||||
motionDurationSlow,
|
||||
} = token;
|
||||
return [
|
||||
{
|
||||
[componentCls]: {
|
||||
'.slick-dots': {
|
||||
position: 'absolute',
|
||||
insetInlineEnd: 0,
|
||||
@ -200,11 +246,11 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
listStyle: 'none',
|
||||
|
||||
'&-bottom': {
|
||||
bottom: carouselDotOffset,
|
||||
bottom: dotOffset,
|
||||
},
|
||||
|
||||
'&-top': {
|
||||
top: carouselDotOffset,
|
||||
top: dotOffset,
|
||||
bottom: 'auto',
|
||||
},
|
||||
|
||||
@ -213,30 +259,30 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
display: 'inline-block',
|
||||
flex: '0 1 auto',
|
||||
boxSizing: 'content-box',
|
||||
width: token.dotWidth,
|
||||
height: token.dotHeight,
|
||||
marginInline: carouselDotMargin,
|
||||
width: dotWidth,
|
||||
height: dotHeight,
|
||||
marginInline: dotGap,
|
||||
padding: 0,
|
||||
textAlign: 'center',
|
||||
textIndent: -999,
|
||||
verticalAlign: 'top',
|
||||
transition: `all ${token.motionDurationSlow}`,
|
||||
transition: `all ${motionDurationSlow}`,
|
||||
|
||||
button: {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: token.dotHeight,
|
||||
height: dotHeight,
|
||||
padding: 0,
|
||||
color: 'transparent',
|
||||
fontSize: 0,
|
||||
background: token.colorBgContainer,
|
||||
background: colorBgContainer,
|
||||
border: 0,
|
||||
borderRadius: token.dotHeight,
|
||||
borderRadius: dotHeight,
|
||||
outline: 'none',
|
||||
cursor: 'pointer',
|
||||
opacity: 0.3,
|
||||
transition: `all ${token.motionDurationSlow}`,
|
||||
opacity: 0.2,
|
||||
transition: `all ${motionDurationSlow}`,
|
||||
|
||||
'&: hover, &:focus': {
|
||||
opacity: 0.75,
|
||||
@ -244,7 +290,7 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
|
||||
'&::after': {
|
||||
position: 'absolute',
|
||||
inset: token.calc(carouselDotMargin).mul(-1).equal(),
|
||||
inset: token.calc(dotGap).mul(-1).equal(),
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
@ -253,7 +299,7 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
width: token.dotActiveWidth,
|
||||
|
||||
'& button': {
|
||||
background: token.colorBgContainer,
|
||||
background: colorBgContainer,
|
||||
opacity: 1,
|
||||
},
|
||||
|
||||
@ -264,11 +310,12 @@ const genCarouselStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
const { componentCls, carouselDotOffset, marginXXS } = token;
|
||||
const { componentCls, dotOffset, arrowOffset, marginXXS } = token;
|
||||
|
||||
const reverseSizeOfDot = {
|
||||
width: token.dotHeight,
|
||||
@ -277,6 +324,26 @@ const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
|
||||
return {
|
||||
[`${componentCls}-vertical`]: {
|
||||
'.slick-prev, .slick-next': {
|
||||
insetInlineStart: '50%',
|
||||
marginBlockStart: 'unset',
|
||||
transform: 'translateX(-50%)',
|
||||
},
|
||||
'.slick-prev': {
|
||||
insetBlockStart: arrowOffset,
|
||||
insetInlineStart: '50%',
|
||||
|
||||
'&::after': {
|
||||
transform: 'rotate(45deg)',
|
||||
},
|
||||
},
|
||||
'.slick-next': {
|
||||
insetBlockStart: 'auto',
|
||||
insetBlockEnd: arrowOffset,
|
||||
'&::after': {
|
||||
transform: 'rotate(-135deg)',
|
||||
},
|
||||
},
|
||||
'.slick-dots': {
|
||||
top: '50%',
|
||||
bottom: 'auto',
|
||||
@ -288,11 +355,11 @@ const genCarouselVerticalStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
|
||||
'&-left': {
|
||||
insetInlineEnd: 'auto',
|
||||
insetInlineStart: carouselDotOffset,
|
||||
insetInlineStart: dotOffset,
|
||||
},
|
||||
|
||||
'&-right': {
|
||||
insetInlineEnd: carouselDotOffset,
|
||||
insetInlineEnd: dotOffset,
|
||||
insetInlineStart: 'auto',
|
||||
},
|
||||
|
||||
@ -343,12 +410,16 @@ const genCarouselRtlStyle: GenerateStyle<CarouselToken> = (token) => {
|
||||
];
|
||||
};
|
||||
|
||||
export const prepareComponentToken: GetDefaultToken<'Carousel'> = () => {
|
||||
export const prepareComponentToken: GetDefaultToken<'Carousel'> = (token) => {
|
||||
const dotActiveWidth = 24;
|
||||
|
||||
return {
|
||||
arrowSize: 16,
|
||||
arrowOffset: token.marginXS,
|
||||
dotWidth: 16,
|
||||
dotHeight: 3,
|
||||
dotGap: token.marginXXS,
|
||||
dotOffset: 12,
|
||||
dotWidthActive: dotActiveWidth,
|
||||
dotActiveWidth,
|
||||
};
|
||||
@ -357,19 +428,13 @@ export const prepareComponentToken: GetDefaultToken<'Carousel'> = () => {
|
||||
// ============================== Export ==============================
|
||||
export default genStyleHooks(
|
||||
'Carousel',
|
||||
(token) => {
|
||||
const { controlHeightLG, controlHeightSM } = token;
|
||||
const carouselToken = mergeToken<CarouselToken>(token, {
|
||||
carouselArrowSize: token.calc(controlHeightLG).div(2).equal(),
|
||||
carouselDotOffset: token.calc(controlHeightSM).div(2).equal(),
|
||||
});
|
||||
|
||||
return [
|
||||
genCarouselStyle(carouselToken),
|
||||
genCarouselVerticalStyle(carouselToken),
|
||||
genCarouselRtlStyle(carouselToken),
|
||||
];
|
||||
},
|
||||
(token) => [
|
||||
genCarouselStyle(token),
|
||||
genArrowsStyle(token),
|
||||
genDotsStyle(token),
|
||||
genCarouselVerticalStyle(token),
|
||||
genCarouselRtlStyle(token),
|
||||
],
|
||||
prepareComponentToken,
|
||||
{
|
||||
deprecatedTokens: [['dotWidthActive', 'dotActiveWidth']],
|
||||
|
@ -1,9 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import type { CascaderProps as RcCascaderProps } from 'rc-cascader';
|
||||
import { Panel } from 'rc-cascader';
|
||||
import type { PickType } from 'rc-cascader/lib/Panel';
|
||||
|
||||
import type { CascaderProps } from '.';
|
||||
import type { CascaderProps, DefaultOptionType } from '.';
|
||||
import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty';
|
||||
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
||||
import useBase from './hooks/useBase';
|
||||
@ -14,9 +15,17 @@ import usePanelStyle from './style/panel';
|
||||
|
||||
export type PanelPickType = Exclude<PickType, 'checkable'> | 'multiple' | 'rootClassName';
|
||||
|
||||
export type CascaderPanelProps = Pick<CascaderProps, PanelPickType>;
|
||||
export type CascaderPanelProps<
|
||||
OptionType extends DefaultOptionType = DefaultOptionType,
|
||||
ValueField extends keyof OptionType = keyof OptionType,
|
||||
Multiple extends boolean = false,
|
||||
> = Pick<CascaderProps<OptionType, ValueField, Multiple>, PanelPickType>;
|
||||
|
||||
const CascaderPanel: React.FC<CascaderPanelProps> = (props) => {
|
||||
function CascaderPanel<
|
||||
OptionType extends DefaultOptionType = DefaultOptionType,
|
||||
ValueField extends keyof OptionType = keyof OptionType,
|
||||
Multiple extends boolean = false,
|
||||
>(props: CascaderPanelProps<OptionType, ValueField, Multiple>) {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
@ -53,7 +62,7 @@ const CascaderPanel: React.FC<CascaderPanelProps> = (props) => {
|
||||
|
||||
return wrapCSSVar(
|
||||
<Panel
|
||||
{...props}
|
||||
{...(props as Pick<RcCascaderProps, PickType>)}
|
||||
checkable={checkable}
|
||||
prefixCls={cascaderPrefixCls}
|
||||
className={classNames(className, hashId, rootClassName, cssVarCls, rootCls)}
|
||||
@ -63,6 +72,6 @@ const CascaderPanel: React.FC<CascaderPanelProps> = (props) => {
|
||||
loadingIcon={loadingIcon}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default CascaderPanel;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { SingleValueType } from 'rc-cascader/lib/Cascader';
|
||||
|
||||
import type { BaseOptionType, DefaultOptionType } from '..';
|
||||
import type { DefaultOptionType } from '..';
|
||||
import Cascader from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import excludeAllWarning from '../../../tests/shared/excludeWarning';
|
||||
@ -71,11 +71,13 @@ const options = [
|
||||
},
|
||||
];
|
||||
|
||||
function filter<OptionType extends BaseOptionType = DefaultOptionType>(
|
||||
function filter<OptionType extends DefaultOptionType = DefaultOptionType>(
|
||||
inputValue: string,
|
||||
path: OptionType[],
|
||||
): boolean {
|
||||
return path.some((option) => option.label.toLowerCase().includes(inputValue.toLowerCase()));
|
||||
return path.some((option) =>
|
||||
option.label?.toString().toLowerCase().includes(inputValue.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
describe('Cascader', () => {
|
||||
@ -183,7 +185,7 @@ describe('Cascader', () => {
|
||||
],
|
||||
},
|
||||
];
|
||||
function customFilter<OptionType extends BaseOptionType = DefaultOptionType>(
|
||||
function customFilter<OptionType extends DefaultOptionType = DefaultOptionType>(
|
||||
inputValue: string,
|
||||
path: OptionType[],
|
||||
): boolean {
|
||||
@ -374,7 +376,7 @@ describe('Cascader', () => {
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: null,
|
||||
children: null as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -516,7 +518,7 @@ describe('Cascader', () => {
|
||||
|
||||
it('onChange works correctly when the label of fieldNames is the same as value', () => {
|
||||
const onChange = jest.fn();
|
||||
const sameNames = { label: 'label', value: 'label' };
|
||||
const sameNames = { label: 'label', value: 'label' } as const;
|
||||
const { container } = render(
|
||||
<Cascader options={options} onChange={onChange} showSearch fieldNames={sameNames} />,
|
||||
);
|
||||
|
@ -80,16 +80,12 @@ describe('Cascader.typescript', () => {
|
||||
});
|
||||
|
||||
it('single onChange', () => {
|
||||
const { container } = render(
|
||||
<Cascader multiple={false} onChange={(values: (string | number)[]) => values} />,
|
||||
);
|
||||
const { container } = render(<Cascader multiple={false} onChange={(values) => values} />);
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it('multiple onChange', () => {
|
||||
const { container } = render(
|
||||
<Cascader multiple onChange={(values: (string | number)[][]) => values} />,
|
||||
);
|
||||
const { container } = render(<Cascader multiple onChange={(values) => values} />);
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
value: string;
|
||||
label: string;
|
||||
children?: Option[];
|
||||
}
|
||||
@ -42,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange = (value: (string | number)[]) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -43,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -34,7 +34,7 @@ const options: Option[] = [
|
||||
const App: React.FC = () => {
|
||||
const [text, setText] = useState('Unselect');
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (_, selectedOptions) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (_, selectedOptions) => {
|
||||
setText(selectedOptions.map((o) => o.label).join(', '));
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -43,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -45,7 +45,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
code: string;
|
||||
@ -43,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -43,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
|
||||
interface Option {
|
||||
@ -24,7 +25,7 @@ const optionLists: Option[] = [
|
||||
const App: React.FC = () => {
|
||||
const [options, setOptions] = useState<Option[]>(optionLists);
|
||||
|
||||
const onChange = (value: (string | number)[], selectedOptions: Option[]) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) => {
|
||||
console.log(value, selectedOptions);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { MultipleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
@ -44,7 +44,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: MultipleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option, 'value', true>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader, Flex } from 'antd';
|
||||
import type { MultipleCascaderProps, SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string | number;
|
||||
@ -43,11 +43,11 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
const onMultipleChange: MultipleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onMultipleChange: CascaderProps<Option, 'value', true>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Cascader } from 'antd';
|
||||
import type { CascaderProps, GetProp } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
type DefaultOptionType = GetProp<CascaderProps, 'options'>[number];
|
||||
|
||||
@ -52,7 +51,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value, selectedOptions) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) => {
|
||||
console.log(value, selectedOptions);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { MultipleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
const { SHOW_CHILD } = Cascader;
|
||||
|
||||
@ -44,7 +44,7 @@ const options: Option[] = [
|
||||
];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const onChange: MultipleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option, 'value', true>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
return (
|
||||
@ -70,7 +70,7 @@ const App: React.FC = () => {
|
||||
onChange={onChange}
|
||||
multiple
|
||||
maxTagCount="responsive"
|
||||
defaultValue={['bamboo']}
|
||||
defaultValue={[['bamboo']]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -43,7 +43,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { Cascader } from 'antd';
|
||||
import type { SingleCascaderProps } from 'antd/es/cascader';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
@ -44,7 +44,7 @@ const options: Option[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const onChange: SingleCascaderProps<Option>['onChange'] = (value) => {
|
||||
const onChange: CascaderProps<Option>['onChange'] = (value) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
|
@ -4,8 +4,7 @@ import type {
|
||||
BaseOptionType,
|
||||
DefaultOptionType,
|
||||
FieldNames,
|
||||
MultipleCascaderProps as RcMultipleCascaderProps,
|
||||
SingleCascaderProps as RcSingleCascaderProps,
|
||||
CascaderProps as RcCascaderProps,
|
||||
ShowSearchType,
|
||||
} from 'rc-cascader';
|
||||
import RcCascader from 'rc-cascader';
|
||||
@ -105,26 +104,12 @@ const defaultSearchRender: ShowSearchType['render'] = (inputValue, path, prefixC
|
||||
return optionList;
|
||||
};
|
||||
|
||||
export type SingleCascaderProps<OptionType extends BaseOptionType = any> = Omit<
|
||||
RcSingleCascaderProps<OptionType>,
|
||||
'checkable' | 'options'
|
||||
> & {
|
||||
multiple?: false;
|
||||
};
|
||||
export type MultipleCascaderProps<OptionType extends BaseOptionType = any> = Omit<
|
||||
RcMultipleCascaderProps<OptionType>,
|
||||
'checkable' | 'options'
|
||||
> & {
|
||||
multiple: true;
|
||||
};
|
||||
|
||||
type UnionCascaderProps<OptionType extends BaseOptionType> =
|
||||
| SingleCascaderProps<OptionType>
|
||||
| MultipleCascaderProps<OptionType>;
|
||||
|
||||
export type CascaderProps<DataNodeType extends BaseOptionType = any> =
|
||||
UnionCascaderProps<DataNodeType> & {
|
||||
multiple?: boolean;
|
||||
export interface CascaderProps<
|
||||
OptionType extends DefaultOptionType = DefaultOptionType,
|
||||
ValueField extends keyof OptionType = keyof OptionType,
|
||||
Multiple extends boolean = false,
|
||||
> extends Omit<RcCascaderProps<OptionType, ValueField, Multiple>, 'checkable'> {
|
||||
multiple?: Multiple;
|
||||
size?: SizeType;
|
||||
/**
|
||||
* @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a
|
||||
@ -136,7 +121,7 @@ export type CascaderProps<DataNodeType extends BaseOptionType = any> =
|
||||
bordered?: boolean;
|
||||
placement?: SelectCommonPlacement;
|
||||
suffixIcon?: React.ReactNode;
|
||||
options?: DataNodeType[];
|
||||
options?: OptionType[];
|
||||
status?: InputStatus;
|
||||
autoClearSearchValue?: boolean;
|
||||
|
||||
@ -149,7 +134,7 @@ export type CascaderProps<DataNodeType extends BaseOptionType = any> =
|
||||
* @default "outlined"
|
||||
*/
|
||||
variant?: Variant;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CascaderRef {
|
||||
focus: () => void;
|
||||
@ -360,8 +345,13 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
|
||||
);
|
||||
|
||||
return wrapCascaderCSSVar(wrapSelectCSSVar(renderNode));
|
||||
}) as unknown as (<OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType>(
|
||||
props: React.PropsWithChildren<CascaderProps<OptionType>> & React.RefAttributes<CascaderRef>,
|
||||
}) as unknown as (<
|
||||
OptionType extends DefaultOptionType = DefaultOptionType,
|
||||
ValueField extends keyof OptionType = keyof OptionType,
|
||||
Multiple extends boolean = false,
|
||||
>(
|
||||
props: React.PropsWithChildren<CascaderProps<OptionType, ValueField, Multiple>> &
|
||||
React.RefAttributes<CascaderRef>,
|
||||
) => React.ReactElement) & {
|
||||
displayName: string;
|
||||
SHOW_PARENT: typeof SHOW_PARENT;
|
||||
|
@ -25,7 +25,17 @@ describe('ConfigProvider', () => {
|
||||
<Button>{text}</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(container.querySelector('span')?.innerHTML).toBe(text);
|
||||
expect(container.querySelector<HTMLSpanElement>('span')?.innerHTML).toBe(text);
|
||||
});
|
||||
|
||||
it('button.autoInsertSpace', () => {
|
||||
const text = '确定';
|
||||
const { container } = render(
|
||||
<ConfigProvider button={{ autoInsertSpace: false }}>
|
||||
<Button>{text}</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(container.querySelector<HTMLSpanElement>('span')?.innerHTML).toBe(text);
|
||||
});
|
||||
|
||||
it('renderEmpty', () => {
|
||||
|
@ -1218,7 +1218,7 @@ describe('ConfigProvider support style and className props', () => {
|
||||
className: 'cp-tabs',
|
||||
style: { backgroundColor: 'red' },
|
||||
addIcon: <span className="cp-test-addIcon">cp-test-addIcon</span>,
|
||||
moreIcon: <span className="cp-test-moreIcon">cp-test-moreIcon</span>,
|
||||
more: { icon: <span className="cp-test-moreIcon">cp-test-moreIcon</span> },
|
||||
removeIcon: <span className="cp-test-removeIcon">cp-test-removeIcon</span>,
|
||||
}}
|
||||
>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button } from 'antd';
|
||||
|
||||
import ConfigProvider from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
@ -61,4 +62,18 @@ describe('ConfigProvider.useConfig', () => {
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deprecated autoInsertSpaceInButton', () => {
|
||||
resetWarned();
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(
|
||||
<ConfigProvider autoInsertSpaceInButton={false}>
|
||||
<Button>测试</Button>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: ConfigProvider] `autoInsertSpaceInButton` is deprecated. Please use `{ button: { autoInsertSpace: boolean }}` instead.',
|
||||
);
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
@ -128,7 +128,7 @@ export type ModalConfig = ComponentStyleConfig &
|
||||
Pick<ModalProps, 'classNames' | 'styles' | 'closeIcon' | 'closable'>;
|
||||
|
||||
export type TabsConfig = ComponentStyleConfig &
|
||||
Pick<TabsProps, 'indicator' | 'indicatorSize' | 'moreIcon' | 'addIcon' | 'removeIcon'>;
|
||||
Pick<TabsProps, 'indicator' | 'indicatorSize' | 'more' | 'moreIcon' | 'addIcon' | 'removeIcon'>;
|
||||
|
||||
export type AlertConfig = ComponentStyleConfig & Pick<AlertProps, 'closable' | 'closeIcon'>;
|
||||
|
||||
@ -140,7 +140,8 @@ export type InputConfig = ComponentStyleConfig &
|
||||
export type TextAreaConfig = ComponentStyleConfig &
|
||||
Pick<TextAreaProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear'>;
|
||||
|
||||
export type ButtonConfig = ComponentStyleConfig & Pick<ButtonProps, 'classNames' | 'styles'>;
|
||||
export type ButtonConfig = ComponentStyleConfig &
|
||||
Pick<ButtonProps, 'classNames' | 'styles' | 'autoInsertSpace'>;
|
||||
|
||||
export type NotificationConfig = ComponentStyleConfig & Pick<ArgsProps, 'closeIcon'>;
|
||||
|
||||
@ -194,6 +195,7 @@ export interface ConfigConsumerProps {
|
||||
* @descEN Set the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config.
|
||||
*/
|
||||
csp?: CSPConfig;
|
||||
/** @deprecated Please use `{ button: { autoInsertSpace: boolean }}` instead */
|
||||
autoInsertSpaceInButton?: boolean;
|
||||
input?: InputConfig;
|
||||
textArea?: TextAreaConfig;
|
||||
|
@ -52,7 +52,6 @@ Some components use dynamic style to support wave effect. You can config `csp` p
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoInsertSpaceInButton | Set false to remove space between 2 chinese characters on Button | boolean | true | |
|
||||
| componentDisabled | Config antd component `disabled` | boolean | - | 4.21.0 |
|
||||
| componentSize | Config antd component size | `small` \| `middle` \| `large` | - | |
|
||||
| csp | Set [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) config | { nonce: string } | - | |
|
||||
@ -105,7 +104,7 @@ const {
|
||||
| avatar | Set Avatar common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| badge | Set Badge common props | { className?: string, style?: React.CSSProperties, classNames?: { count?: string, indicator?: string }, styles?: { count?: React.CSSProperties, indicator?: React.CSSProperties } } | - | 5.7.0 |
|
||||
| breadcrumb | Set Breadcrumb common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| button | Set Button common props | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties } } | - | 5.6.0 |
|
||||
| button | Set Button common props | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties }, autoInsertSpace?: boolean } | - | 5.6.0, autoInsertSpace: 5.17.0 |
|
||||
| card | Set Card common props | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card#api), styles?: [CardProps\["styles"\]](/components/card#api) } | - | 5.7.0, `classNames` and `styles`: 5.14.0 |
|
||||
| calendar | Set Calendar common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| carousel | Set Carousel common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
|
@ -4,7 +4,7 @@ import IconContext from '@ant-design/icons/lib/components/Context';
|
||||
import useMemo from 'rc-util/lib/hooks/useMemo';
|
||||
import { merge } from 'rc-util/lib/utils/set';
|
||||
|
||||
import warning, { WarningContext } from '../_util/warning';
|
||||
import warning, { devUseWarning, WarningContext } from '../_util/warning';
|
||||
import type { WarningContextProps } from '../_util/warning';
|
||||
import ValidateMessagesContext from '../form/validateMessagesContext';
|
||||
import type { Locale } from '../locale';
|
||||
@ -122,6 +122,7 @@ export interface ConfigProviderProps {
|
||||
children?: React.ReactNode;
|
||||
renderEmpty?: RenderEmptyHandler;
|
||||
csp?: CSPConfig;
|
||||
/** @deprecated Please use `{ button: { autoInsertSpace: boolean }}` instead */
|
||||
autoInsertSpaceInButton?: boolean;
|
||||
form?: FormConfig;
|
||||
input?: InputConfig;
|
||||
@ -463,6 +464,15 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
|
||||
floatButtonGroup,
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warningFn = devUseWarning('ConfigProvider');
|
||||
warningFn(
|
||||
!('autoInsertSpaceInButton' in props),
|
||||
'deprecated',
|
||||
'`autoInsertSpaceInButton` is deprecated. Please use `{ button: { autoInsertSpace: boolean }}` instead.',
|
||||
);
|
||||
}
|
||||
|
||||
const config: ConfigConsumerProps = {
|
||||
...parentContext,
|
||||
};
|
||||
@ -482,6 +492,14 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof autoInsertSpaceInButton !== 'undefined') {
|
||||
// merge deprecated api
|
||||
config.button = {
|
||||
autoInsertSpace: autoInsertSpaceInButton,
|
||||
...config.button,
|
||||
};
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/27617
|
||||
const memoedConfig = useMemo(
|
||||
() => config,
|
||||
|
@ -53,7 +53,6 @@ export default Demo;
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoInsertSpaceInButton | 设置为 `false` 时,移除按钮中 2 个汉字之间的空格 | boolean | true | |
|
||||
| componentDisabled | 设置 antd 组件禁用状态 | boolean | - | 4.21.0 |
|
||||
| componentSize | 设置 antd 组件大小 | `small` \| `middle` \| `large` | - | |
|
||||
| csp | 设置 [Content Security Policy](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP) 配置 | { nonce: string } | - | |
|
||||
@ -107,7 +106,7 @@ const {
|
||||
| avatar | 设置 Avatar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| badge | 设置 Badge 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: { count?: string, indicator?: string }, styles?: { count?: React.CSSProperties, indicator?: React.CSSProperties } } | - | 5.7.0 |
|
||||
| breadcrumb | 设置 Breadcrumb 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| button | 设置 Button 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties } } | - | 5.6.0 |
|
||||
| button | 设置 Button 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: { icon: string }, styles?: { icon: React.CSSProperties }, autoInsertSpace?: boolean } | - | 5.6.0, autoInsertSpace: 5.17.0 |
|
||||
| calendar | 设置 Calendar 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
| card | 设置 Card 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card-cn#api), styles?: [CardProps\["styles"\]](/components/card-cn#api) } | - | 5.7.0, `classNames` 和 `styles`: 5.14.0 |
|
||||
| carousel | 设置 Carousel 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
|
||||
|
@ -212,7 +212,7 @@ Added in `4.1.0`.
|
||||
| defaultPickerValue | Default panel date, will be reset when panel open | [dayjs](https://day.js.org/) | - | 5.14.0 |
|
||||
| defaultValue | To set default date | \[[dayjs](https://day.js.org/), [dayjs](https://day.js.org/)] | - | |
|
||||
| disabled | If disable start or end | \[boolean, boolean] | - | |
|
||||
| disabledTime | To specify the time that cannot be selected | function(date: dayjs, partial: `start` \| `end`) | - | |
|
||||
| disabledTime | To specify the time that cannot be selected | function(date: dayjs, partial: `start` \| `end`, info: { from?: dayjs }) | - | `info.from`: 5.17.0 |
|
||||
| format | To set the date format. refer to [dayjs#format](https://day.js.org/docs/en/display/format) | [formatType](#formattype) | `YYYY-MM-DD HH:mm:ss` | |
|
||||
| id | Config input ids | { start?: string, end?: string } | - | 5.14.0 |
|
||||
| pickerValue | Panel date. Used for controlled switching of panel date. Work with `onPanelChange` | [dayjs](https://day.js.org/) | - | 5.14.0 |
|
||||
|
@ -213,7 +213,7 @@ dayjs.locale('zh-cn');
|
||||
| defaultPickerValue | 默认面板日期,每次面板打开时会被重置到该日期 | [dayjs](https://day.js.org/)[] | - | 5.14.0 |
|
||||
| defaultValue | 默认日期 | [dayjs](https://day.js.org/)\[] | - | |
|
||||
| disabled | 禁用起始项 | \[boolean, boolean] | - | |
|
||||
| disabledTime | 不可选择的时间 | function(date: dayjs, partial: `start` \| `end`) | - | |
|
||||
| disabledTime | 不可选择的时间 | function(date: dayjs, partial: `start` \| `end`, info: { from?: dayjs }) | - | `info.from`: 5.17.0 |
|
||||
| format | 展示的日期格式,配置参考 [dayjs#format](https://day.js.org/docs/zh-CN/display/format#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%8D%A0%E4%BD%8D%E7%AC%A6%E5%88%97%E8%A1%A8)。 | [formatType](#formattype) | `YYYY-MM-DD HH:mm:ss` | |
|
||||
| id | 设置输入框 `id` 属性。 | { start?: string, end?: string } | - | 5.14.0 |
|
||||
| pickerValue | 面板日期,可以用于受控切换面板所在日期。配合 `onPanelChange` 使用。 | [dayjs](https://day.js.org/)[] | - | 5.14.0 |
|
||||
|
@ -308,10 +308,8 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
|
||||
width: pickerControlIconSize,
|
||||
height: pickerControlIconSize,
|
||||
border: `0 solid currentcolor`,
|
||||
borderBlockStartWidth: pickerControlIconBorderWidth,
|
||||
borderBlockEndWidth: 0,
|
||||
borderInlineStartWidth: pickerControlIconBorderWidth,
|
||||
borderInlineEndWidth: 0,
|
||||
borderBlockWidth: `${unit(pickerControlIconBorderWidth)} 0`,
|
||||
borderInlineWidth: `${unit(pickerControlIconBorderWidth)} 0`,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
@ -326,10 +324,8 @@ export const genPanelStyle = (token: SharedPickerToken): CSSObject => {
|
||||
width: pickerControlIconSize,
|
||||
height: pickerControlIconSize,
|
||||
border: '0 solid currentcolor',
|
||||
borderBlockStartWidth: pickerControlIconBorderWidth,
|
||||
borderBlockEndWidth: 0,
|
||||
borderInlineStartWidth: pickerControlIconBorderWidth,
|
||||
borderInlineEndWidth: 0,
|
||||
borderBlockWidth: `${unit(pickerControlIconBorderWidth)} 0`,
|
||||
borderInlineWidth: `${unit(pickerControlIconBorderWidth)} 0`,
|
||||
content: '""',
|
||||
},
|
||||
},
|
||||
|
@ -5,6 +5,8 @@ import type { DrawerProps as RCDrawerProps } from 'rc-drawer';
|
||||
import useClosable, { pickClosable } from '../_util/hooks/useClosable';
|
||||
import type { ClosableType } from '../_util/hooks/useClosable';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Spin from '../spin';
|
||||
import type { SpinProps } from '../spin';
|
||||
|
||||
export interface DrawerClassNames extends NonNullable<RCDrawerProps['classNames']> {
|
||||
header?: string;
|
||||
@ -38,6 +40,7 @@ export interface DrawerPanelProps {
|
||||
children?: React.ReactNode;
|
||||
classNames?: DrawerClassNames;
|
||||
styles?: DrawerStyles;
|
||||
loading?: boolean | Omit<SpinProps, 'fullscreen' | 'tip'>;
|
||||
|
||||
/** @deprecated Please use `styles.header` instead */
|
||||
headerStyle?: React.CSSProperties;
|
||||
@ -59,6 +62,7 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
||||
title,
|
||||
footer,
|
||||
extra,
|
||||
loading,
|
||||
onClose,
|
||||
headerStyle,
|
||||
bodyStyle,
|
||||
@ -87,6 +91,19 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
||||
},
|
||||
);
|
||||
|
||||
// >>>>>>>>> Spinning
|
||||
let spinProps: SpinProps | undefined;
|
||||
if (typeof loading === 'boolean') {
|
||||
spinProps = {
|
||||
spinning: loading,
|
||||
};
|
||||
} else if (typeof loading === 'object') {
|
||||
spinProps = {
|
||||
spinning: true,
|
||||
...loading,
|
||||
};
|
||||
}
|
||||
|
||||
const headerNode = React.useMemo<React.ReactNode>(() => {
|
||||
if (!title && !mergedClosable) {
|
||||
return null;
|
||||
@ -139,6 +156,21 @@ const DrawerPanel: React.FC<DrawerPanelProps> = (props) => {
|
||||
);
|
||||
}, [footer, footerStyle, prefixCls]);
|
||||
|
||||
if (spinProps?.spinning) {
|
||||
return (
|
||||
<Spin
|
||||
spinning={false}
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
{...spinProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerNode}
|
||||
|
@ -178,6 +178,35 @@ describe('Drawer', () => {
|
||||
expect(baseElement.querySelectorAll('button.forceRender').length).toBe(1);
|
||||
});
|
||||
|
||||
describe('Drawer loading', () => {
|
||||
it('have a spinner', () => {
|
||||
const { container: wrapper } = render(
|
||||
<Drawer open loading getContainer={false}>
|
||||
Here is content of Drawer
|
||||
</Drawer>,
|
||||
);
|
||||
|
||||
triggerMotion();
|
||||
expect(wrapper.firstChild).toMatchSnapshot();
|
||||
});
|
||||
it('have a custom loading', () => {
|
||||
const loadingContent = 'Custom Loading...';
|
||||
const { container: wrapper } = render(
|
||||
<Drawer
|
||||
open
|
||||
loading={{ indicator: <span>{loadingContent}</span>, spinning: true }}
|
||||
getContainer={false}
|
||||
>
|
||||
Here is content of Drawer
|
||||
</Drawer>,
|
||||
);
|
||||
|
||||
triggerMotion();
|
||||
const [loadingWrapper] = wrapper.getElementsByClassName('ant-spin-dot');
|
||||
expect(loadingWrapper).toHaveTextContent(loadingContent);
|
||||
});
|
||||
});
|
||||
|
||||
it('support closeIcon', () => {
|
||||
const { container: wrapper } = render(
|
||||
<Drawer open closable closeIcon={<span>close</span>} width={400} getContainer={false}>
|
||||
|
@ -1,5 +1,62 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Drawer Drawer loading have a spinner 1`] = `
|
||||
<div
|
||||
class="ant-drawer ant-drawer-right ant-drawer-open ant-drawer-inline"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-mask"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-sentinel="start"
|
||||
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="ant-drawer-content-wrapper"
|
||||
style="width: 378px;"
|
||||
>
|
||||
<div
|
||||
aria-modal="true"
|
||||
class="ant-drawer-content"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
aria-busy="true"
|
||||
aria-live="polite"
|
||||
class="ant-spin ant-spin-spinning"
|
||||
style="height: 100%; display: flex; justify-content: center; align-items: center;"
|
||||
>
|
||||
<span
|
||||
class="ant-spin-dot ant-spin-dot-spin"
|
||||
>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-sentinel="end"
|
||||
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Drawer className is test_drawer 1`] = `
|
||||
<div
|
||||
class="ant-drawer ant-drawer-right test_drawer ant-drawer-open ant-drawer-inline"
|
||||
|
@ -2781,6 +2781,75 @@ Array [
|
||||
|
||||
exports[`renders components/drawer/demo/form-in-drawer.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/drawer/demo/loading.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-drawer ant-drawer-right ant-drawer-open ant-drawer-inline"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="ant-drawer-mask"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-sentinel="start"
|
||||
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="ant-drawer-content-wrapper"
|
||||
style="width: 378px;"
|
||||
>
|
||||
<div
|
||||
aria-modal="true"
|
||||
class="ant-drawer-content"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
aria-busy="true"
|
||||
aria-live="polite"
|
||||
class="ant-spin ant-spin-spinning"
|
||||
style="height: 100%; display: flex; justify-content: center; align-items: center;"
|
||||
>
|
||||
<span
|
||||
class="ant-spin-dot ant-spin-dot-spin"
|
||||
>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
<i
|
||||
class="ant-spin-dot-item"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
data-sentinel="end"
|
||||
style="width: 0px; height: 0px; overflow: hidden; outline: none; position: absolute;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/loading.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/drawer/demo/multi-level-drawer.tsx extend context correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
|
@ -259,6 +259,17 @@ exports[`renders components/drawer/demo/form-in-drawer.tsx correctly 1`] = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/loading.tsx correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders components/drawer/demo/multi-level-drawer.tsx correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
|
7
components/drawer/demo/loading.md
Normal file
7
components/drawer/demo/loading.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
设置抽屉加载状态。
|
||||
|
||||
## en-US
|
||||
|
||||
Set the loading status of Drawer.
|
54
components/drawer/demo/loading.tsx
Normal file
54
components/drawer/demo/loading.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { DrawerProps } from 'antd';
|
||||
import { Button, Drawer } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState<DrawerProps['loading']>(true);
|
||||
let id: NodeJS.Timer;
|
||||
|
||||
const showDrawer = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false);
|
||||
clearTimeout(Number(id));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
id = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="primary" onClick={showDrawer}>
|
||||
Open
|
||||
</Button>
|
||||
<Drawer
|
||||
title="Basic Drawer"
|
||||
placement="right"
|
||||
closable={false}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
loading={loading}
|
||||
afterOpenChange={(visible) => !visible && setLoading(true)}
|
||||
>
|
||||
<Button onClick={() => setLoading(true)}>set Loading true</Button>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -22,6 +22,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic-right.tsx">Basic</code>
|
||||
<code src="./demo/placement.tsx">Custom Placement</code>
|
||||
<code src="./demo/loading.tsx">Loading</code>
|
||||
<code src="./demo/extra.tsx">Extra Actions</code>
|
||||
<code src="./demo/render-in-current.tsx">Render in current dom</code>
|
||||
<code src="./demo/form-in-drawer.tsx">Submit form in drawer</code>
|
||||
@ -68,6 +69,7 @@ v5 use `rootClassName` & `rootStyle` to config wrapper style instead of `classNa
|
||||
| styles | Semantic structure style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.10.0 |
|
||||
| size | preset size of drawer, default `378px` and large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
|
||||
| title | The title for Drawer | ReactNode | - | |
|
||||
| loading | Show spinning indicator | boolean \| `Omit<SpinProp, 'fullScreen' \| 'tip'>` | false | 5.17.0 |
|
||||
| open | Whether the Drawer dialog is visible or not | boolean | false | |
|
||||
| width | Width of the Drawer dialog | string \| number | 378 | |
|
||||
| zIndex | The `z-index` of the Drawer | number | 1000 | |
|
||||
|
@ -22,6 +22,7 @@ demo:
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic-right.tsx">基础抽屉</code>
|
||||
<code src="./demo/placement.tsx">自定义位置</code>
|
||||
<code src="./demo/loading.tsx">加载中</code>
|
||||
<code src="./demo/extra.tsx">额外操作</code>
|
||||
<code src="./demo/render-in-current.tsx">渲染在当前 DOM</code>
|
||||
<code src="./demo/form-in-drawer.tsx">抽屉表单</code>
|
||||
@ -67,6 +68,7 @@ v5 使用 `rootClassName` 与 `rootStyle` 来配置最外层元素样式。原 v
|
||||
| style | 设计 Drawer 容器样式,如果你只需要设置内容部分请使用 `bodyStyle` | CSSProperties | - | |
|
||||
| styles | 语义化结构 style | [Record<SemanticDOM, CSSProperties>](#semantic-dom) | - | 5.10.0 |
|
||||
| title | 标题 | ReactNode | - | |
|
||||
| loading | 显示旋转指示器 | boolean \| `Omit<SpinProp, 'fullScreen' \| 'tip'>` | false | 5.17.0 |
|
||||
| open | Drawer 是否可见 | boolean | - |
|
||||
| width | 宽度 | string \| number | 378 | |
|
||||
| zIndex | 设置 Drawer 的 `z-index` | number | 1000 | |
|
||||
|
@ -67,4 +67,19 @@ describe('Flex', () => {
|
||||
'ant-flex-align-center',
|
||||
);
|
||||
});
|
||||
|
||||
it('wrap prop shouled support boolean', () => {
|
||||
const { container, rerender } = render(<Flex>test</Flex>);
|
||||
const element = container.querySelector<HTMLDivElement>('.ant-flex');
|
||||
|
||||
([true, 'wrap'] as const).forEach((value) => {
|
||||
rerender(<Flex wrap={value}>test</Flex>);
|
||||
expect(element).toHaveClass('ant-flex-wrap-wrap');
|
||||
});
|
||||
|
||||
([false, 'nowrap'] as const).forEach((value) => {
|
||||
rerender(<Flex wrap={value}>test</Flex>);
|
||||
expect(element).not.toHaveClass('ant-flex-wrap-wrap');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Button, Flex } from 'antd';
|
||||
|
||||
const Demo: React.FC = () => (
|
||||
<Flex wrap="wrap" gap="small">
|
||||
<Flex wrap gap="small">
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<Button key={i} type="primary">
|
||||
Button
|
||||
|
@ -37,10 +37,10 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| Property | Description | type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| vertical | Is direction of the flex vertical, use `flex-direction: column` | boolean | `false` | |
|
||||
| wrap | Set whether the element is displayed in a single line or in multiple lines | reference [flex-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap) | nowrap | |
|
||||
| justify | Sets the alignment of elements in the direction of the main axis | reference [justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content) | normal | |
|
||||
| align | Sets the alignment of elements in the direction of the cross axis | reference [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) | normal | |
|
||||
| flex | flex CSS shorthand properties | reference [flex](https://developer.mozilla.org/en-US/docs/Web/CSS/flex) | normal | |
|
||||
| wrap | Set whether the element is displayed in a single line or in multiple lines | [flex-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap) \| boolean | nowrap | boolean: 5.17.0 |
|
||||
| justify | Sets the alignment of elements in the direction of the main axis | [justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content) | normal | |
|
||||
| align | Sets the alignment of elements in the direction of the cross axis | [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items) | normal | |
|
||||
| flex | flex CSS shorthand properties | [flex](https://developer.mozilla.org/en-US/docs/Web/CSS/flex) | normal | |
|
||||
| gap | Sets the gap between grids | `small` \| `middle` \| `large` \| string \| number | - | |
|
||||
| component | custom element type | React.ComponentType | `div` | |
|
||||
|
||||
|
@ -38,10 +38,10 @@ tag: 5.10.0
|
||||
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| vertical | flex 主轴的方向是否垂直,使用 `flex-direction: column` | boolean | `false` |
|
||||
| wrap | 设置元素单行显示还是多行显示 | 参考 [flex-wrap](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-wrap) | nowrap | |
|
||||
| justify | 设置元素在主轴方向上的对齐方式 | 参考 [justify-content](https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content) | normal | |
|
||||
| align | 设置元素在交叉轴方向上的对齐方式 | 参考 [align-items](https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items) | normal | |
|
||||
| flex | flex CSS 简写属性 | 参考 [flex](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex) | normal | |
|
||||
| wrap | 设置元素单行显示还是多行显示 | [flex-wrap](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-wrap) \| boolean | nowrap | boolean: 5.17.0 |
|
||||
| justify | 设置元素在主轴方向上的对齐方式 | [justify-content](https://developer.mozilla.org/zh-CN/docs/Web/CSS/justify-content) | normal | |
|
||||
| align | 设置元素在交叉轴方向上的对齐方式 | [align-items](https://developer.mozilla.org/zh-CN/docs/Web/CSS/align-items) | normal | |
|
||||
| flex | flex CSS 简写属性 | [flex](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex) | normal | |
|
||||
| gap | 设置网格之间的间隙 | `small` \| `middle` \| `large` \| string \| number | - | |
|
||||
| component | 自定义元素类型 | React.ComponentType | `div` | |
|
||||
|
||||
|
@ -7,7 +7,7 @@ export interface FlexProps<P = AnyObject> extends React.HTMLAttributes<HTMLEleme
|
||||
prefixCls?: string;
|
||||
rootClassName?: string;
|
||||
vertical?: boolean;
|
||||
wrap?: React.CSSProperties['flexWrap'];
|
||||
wrap?: boolean | React.CSSProperties['flexWrap'];
|
||||
justify?: React.CSSProperties['justifyContent'];
|
||||
align?: React.CSSProperties['alignItems'];
|
||||
flex?: React.CSSProperties['flex'];
|
||||
|
@ -2,9 +2,9 @@ import classNames from 'classnames';
|
||||
|
||||
import type { FlexProps } from './interface';
|
||||
|
||||
export const flexWrapValues = ['wrap', 'nowrap', 'wrap-reverse'] as const;
|
||||
export const flexWrapValues: React.CSSProperties['flexWrap'][] = ['wrap', 'nowrap', 'wrap-reverse'];
|
||||
|
||||
export const justifyContentValues = [
|
||||
export const justifyContentValues: React.CSSProperties['justifyContent'][] = [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'start',
|
||||
@ -17,9 +17,9 @@ export const justifyContentValues = [
|
||||
'normal',
|
||||
'left',
|
||||
'right',
|
||||
] as const;
|
||||
];
|
||||
|
||||
export const alignItemsValues = [
|
||||
export const alignItemsValues: React.CSSProperties['alignItems'][] = [
|
||||
'center',
|
||||
'start',
|
||||
'end',
|
||||
@ -30,14 +30,13 @@ export const alignItemsValues = [
|
||||
'baseline',
|
||||
'normal',
|
||||
'stretch',
|
||||
] as const;
|
||||
];
|
||||
|
||||
const genClsWrap = (prefixCls: string, props: FlexProps) => {
|
||||
const wrapCls: Record<PropertyKey, boolean> = {};
|
||||
flexWrapValues.forEach((cssKey) => {
|
||||
wrapCls[`${prefixCls}-wrap-${cssKey}`] = props.wrap === cssKey;
|
||||
});
|
||||
return wrapCls;
|
||||
const wrap = props.wrap === true ? 'wrap' : props.wrap;
|
||||
return {
|
||||
[`${prefixCls}-wrap-${wrap}`]: wrap && flexWrapValues.includes(wrap),
|
||||
};
|
||||
};
|
||||
|
||||
const genClsAlign = (prefixCls: string, props: FlexProps) => {
|
||||
|
@ -2143,7 +2143,9 @@ exports[`renders components/form/demo/customized-form-controls.tsx extend contex
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
id="customized_form_controls_price"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined"
|
||||
style="width: 100px;"
|
||||
@ -22212,6 +22214,380 @@ exports[`renders components/form/demo/validate-other.tsx extend context correctl
|
||||
|
||||
exports[`renders components/form/demo/validate-other.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/form/demo/validate-scroll-to-field.tsx extend context correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
style="padding-block: 32px;"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-offset-6 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Scroll to Bio
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="username"
|
||||
title="UserName"
|
||||
>
|
||||
UserName
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="username"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="occupation"
|
||||
title="Occupation"
|
||||
>
|
||||
Occupation
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined ant-select-in-form-item ant-select-single ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="occupation_list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="occupation_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="occupation"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity: 0;"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomLeft"
|
||||
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="occupation_list"
|
||||
role="listbox"
|
||||
style="height: 0px; width: 0px; overflow: hidden;"
|
||||
>
|
||||
<div
|
||||
aria-label="Designer"
|
||||
aria-selected="false"
|
||||
id="occupation_list_0"
|
||||
role="option"
|
||||
>
|
||||
designer
|
||||
</div>
|
||||
<div
|
||||
aria-label="Developer"
|
||||
aria-selected="false"
|
||||
id="occupation_list_1"
|
||||
role="option"
|
||||
>
|
||||
developer
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rc-virtual-list"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="rc-virtual-list-holder"
|
||||
style="max-height: 256px; overflow-y: auto;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="rc-virtual-list-holder-inner"
|
||||
style="display: flex; flex-direction: column;"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option ant-select-item-option-active"
|
||||
title="Designer"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Designer
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="Developer"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Developer
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="Product Manager"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
Product Manager
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="motto"
|
||||
title="Motto"
|
||||
>
|
||||
Motto
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<textarea
|
||||
class="ant-input ant-input-outlined"
|
||||
id="motto"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="bio"
|
||||
title="Bio"
|
||||
>
|
||||
Bio
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<textarea
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="bio"
|
||||
rows="6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-offset-6 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<div
|
||||
class="ant-flex ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reset
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/validate-scroll-to-field.tsx extend context correctly 2`] = `[]`;
|
||||
|
||||
exports[`renders components/form/demo/validate-static.tsx extend context correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
|
@ -1624,7 +1624,9 @@ exports[`renders components/form/demo/customized-form-controls.tsx correctly 1`]
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<span>
|
||||
<span
|
||||
id="customized_form_controls_price"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined"
|
||||
style="width:100px"
|
||||
@ -9878,6 +9880,281 @@ exports[`renders components/form/demo/validate-other.tsx correctly 1`] = `
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/validate-scroll-to-field.tsx correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
style="padding-block:32px"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-offset-6 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-default"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Scroll to Bio
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="username"
|
||||
title="UserName"
|
||||
>
|
||||
UserName
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<input
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="username"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="occupation"
|
||||
title="Occupation"
|
||||
>
|
||||
Occupation
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<div
|
||||
class="ant-select ant-select-outlined ant-select-in-form-item ant-select-single ant-select-show-arrow"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="occupation_list"
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="occupation_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="occupation"
|
||||
readonly=""
|
||||
role="combobox"
|
||||
style="opacity:0"
|
||||
type="search"
|
||||
unselectable="on"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-arrow"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-select-suffix"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class=""
|
||||
for="motto"
|
||||
title="Motto"
|
||||
>
|
||||
Motto
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<textarea
|
||||
class="ant-input ant-input-outlined"
|
||||
id="motto"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-6 ant-form-item-label"
|
||||
>
|
||||
<label
|
||||
class="ant-form-item-required"
|
||||
for="bio"
|
||||
title="Bio"
|
||||
>
|
||||
Bio
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-14 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<textarea
|
||||
aria-required="true"
|
||||
class="ant-input ant-input-outlined"
|
||||
id="bio"
|
||||
rows="6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-form-item"
|
||||
>
|
||||
<div
|
||||
class="ant-row ant-form-item-row"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-offset-6 ant-form-item-control"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input"
|
||||
>
|
||||
<div
|
||||
class="ant-form-item-control-input-content"
|
||||
>
|
||||
<div
|
||||
class="ant-flex ant-flex-gap-small"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
<span>
|
||||
Submit
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="ant-btn ant-btn-default ant-btn-dangerous"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reset
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`renders components/form/demo/validate-static.tsx correctly 1`] = `
|
||||
<form
|
||||
class="ant-form ant-form-horizontal"
|
||||
|
@ -448,7 +448,8 @@ describe('Form', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('scrollToFirstError', async () => {
|
||||
describe('scrollToFirstError', () => {
|
||||
it('should work with scrollToFirstError', async () => {
|
||||
const onFinishFailed = jest.fn();
|
||||
|
||||
const { container } = render(
|
||||
@ -474,6 +475,92 @@ describe('Form', () => {
|
||||
expect(onFinishFailed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should work with scrollToFirstError with ref', async () => {
|
||||
const ForwardRefInput = React.forwardRef<HTMLInputElement, any>(({ id, ...props }, ref) => (
|
||||
<input {...props} ref={ref} />
|
||||
));
|
||||
|
||||
const NativeInput = React.forwardRef<any, any>(({ id, ...props }, ref) => {
|
||||
const internalRef = React.useRef<HTMLInputElement>(null);
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
nativeElement: internalRef.current,
|
||||
}));
|
||||
return <input {...props} ref={internalRef} />;
|
||||
});
|
||||
|
||||
const NormalInput = (props: any) => <input {...props} />;
|
||||
|
||||
const { getByRole, getAllByRole } = render(
|
||||
<Form scrollToFirstError>
|
||||
<Form.Item name="foo" rules={[{ required: true }]}>
|
||||
<ForwardRefInput />
|
||||
</Form.Item>
|
||||
<Form.Item name="bar" rules={[{ required: true }]}>
|
||||
<NativeInput />
|
||||
</Form.Item>
|
||||
<Form.Item name="baz" rules={[{ required: true }]}>
|
||||
<NormalInput />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
|
||||
// click submit to trigger validate
|
||||
const allInputs = getAllByRole('textbox');
|
||||
const button = getByRole('button');
|
||||
expect(allInputs).toHaveLength(3);
|
||||
|
||||
fireEvent.click(button);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(scrollIntoView).toHaveBeenNthCalledWith(1, allInputs[0], expect.any(Object));
|
||||
|
||||
// change the value of the first input
|
||||
fireEvent.change(allInputs[0], { target: { value: '123' } });
|
||||
fireEvent.click(button);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(scrollIntoView).toHaveBeenNthCalledWith(2, allInputs[1], expect.any(Object));
|
||||
|
||||
// change the value of the second input
|
||||
fireEvent.change(allInputs[1], { target: { value: 'abc' } });
|
||||
fireEvent.click(button);
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(scrollIntoView).toHaveBeenNthCalledWith(3, allInputs[2], expect.any(Object));
|
||||
|
||||
expect(scrollIntoView).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/28869
|
||||
it('should work with Upload', async () => {
|
||||
const uploadRef = React.createRef<any>();
|
||||
|
||||
const { getByRole } = render(
|
||||
<Form scrollToFirstError>
|
||||
<Form.Item
|
||||
name="demo-form_dragger"
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => (Array.isArray(e) ? e : e?.fileList)}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Upload name="files" action="/upload.do" ref={uploadRef} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button htmlType="submit">Submit</Button>
|
||||
</Form.Item>
|
||||
</Form>,
|
||||
);
|
||||
fireEvent.click(getByRole('button'));
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(scrollIntoView).toHaveBeenCalled();
|
||||
expect((scrollIntoView as any).mock.calls[0][0]).toBe(uploadRef.current.nativeElement);
|
||||
});
|
||||
});
|
||||
|
||||
it('Form.Item should support data-*、aria-* and custom attribute', () => {
|
||||
const { container } = render(
|
||||
<Form>
|
||||
|
@ -13,6 +13,7 @@ const App: React.FC = () => (
|
||||
labelColonMarginInlineStart: 4,
|
||||
labelColonMarginInlineEnd: 12,
|
||||
itemMarginBottom: 18,
|
||||
inlineItemMarginBottom: 18,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
@ -2,12 +2,14 @@
|
||||
|
||||
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
|
||||
|
||||
> - 提供受控属性 `value` 或其它与 [`valuePropName`](https://ant.design/components/form-cn/#formitem) 的值同名的属性。
|
||||
> - 提供 `onChange` 事件或 [`trigger`](https://ant.design/components/form-cn/#formitem) 的值同名的事件。
|
||||
> - 提供受控属性 `value` 或其它与 [`valuePropName`](#formitem) 的值同名的属性。
|
||||
> - 提供 `onChange` 事件或 [`trigger`](#formitem) 的值同名的事件。
|
||||
> - 转发 ref 或者传递 id 属性到 dom 以支持 `scrollToField` 方法。
|
||||
|
||||
## en-US
|
||||
|
||||
Customized or third-party form controls can be used in Form, too. Controls must follow these conventions:
|
||||
|
||||
> - It has a controlled property `value` or other name which is equal to the value of [`valuePropName`](https://ant.design/components/form/#formitem).
|
||||
> - It has event `onChange` or an event which name is equal to the value of [`trigger`](https://ant.design/components/form/#formitem).
|
||||
> - It has a controlled property `value` or other name which is equal to the value of [`valuePropName`](#formitem).
|
||||
> - It has event `onChange` or an event which name is equal to the value of [`trigger`](#formitem).
|
||||
> - Forward the ref or pass the id property to dom to support the `scrollToField` method.
|
||||
|
@ -11,11 +11,13 @@ interface PriceValue {
|
||||
}
|
||||
|
||||
interface PriceInputProps {
|
||||
id?: string;
|
||||
value?: PriceValue;
|
||||
onChange?: (value: PriceValue) => void;
|
||||
}
|
||||
|
||||
const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
|
||||
const PriceInput: React.FC<PriceInputProps> = (props) => {
|
||||
const { id, value = {}, onChange } = props;
|
||||
const [number, setNumber] = useState(0);
|
||||
const [currency, setCurrency] = useState<Currency>('rmb');
|
||||
|
||||
@ -42,7 +44,7 @@ const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span id={id}>
|
||||
<Input
|
||||
type="text"
|
||||
value={value.number || number}
|
||||
|
7
components/form/demo/validate-scroll-to-field.md
Normal file
7
components/form/demo/validate-scroll-to-field.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
校验失败时/手动滚动到错误字段。
|
||||
|
||||
## en-US
|
||||
|
||||
When validation fails or manually scroll to the error field.
|
55
components/form/demo/validate-scroll-to-field.tsx
Normal file
55
components/form/demo/validate-scroll-to-field.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Button, Flex, Form, Input, Select } from 'antd';
|
||||
|
||||
const App = () => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
scrollToFirstError
|
||||
style={{ paddingBlock: 32 }}
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 14 }}
|
||||
>
|
||||
<Form.Item wrapperCol={{ offset: 6 }}>
|
||||
<Button onClick={() => form.scrollToField('bio')}>Scroll to Bio</Button>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="username" label="UserName" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Occupation" name="occupation">
|
||||
<Select
|
||||
options={[
|
||||
{ label: 'Designer', value: 'designer' },
|
||||
{ label: 'Developer', value: 'developer' },
|
||||
{ label: 'Product Manager', value: 'product-manager' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="motto" label="Motto">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="bio" label="Bio" rules={[{ required: true }]}>
|
||||
<Input.TextArea rows={6} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 6 }}>
|
||||
<Flex gap="small">
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button danger onClick={() => form.resetFields()}>
|
||||
Reset
|
||||
</Button>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -23,6 +23,23 @@ function toNamePathStr(name: NamePath) {
|
||||
return namePath.join('_');
|
||||
}
|
||||
|
||||
function getFieldDOMNode(name: NamePath, wrapForm: FormInstance) {
|
||||
const field = wrapForm.getFieldInstance(name);
|
||||
|
||||
if (field instanceof HTMLElement) {
|
||||
return field;
|
||||
}
|
||||
|
||||
if (field?.nativeElement instanceof HTMLElement) {
|
||||
return field.nativeElement;
|
||||
}
|
||||
|
||||
const fieldId = getFieldId(toArray(name), wrapForm.__INTERNAL__.name);
|
||||
if (fieldId) {
|
||||
return document.getElementById(fieldId);
|
||||
}
|
||||
}
|
||||
|
||||
export default function useForm<Values = any>(form?: FormInstance<Values>): [FormInstance<Values>] {
|
||||
const [rcForm] = useRcForm();
|
||||
const itemsRef = React.useRef<Record<string, React.ReactElement>>({});
|
||||
@ -42,9 +59,7 @@ export default function useForm<Values = any>(form?: FormInstance<Values>): [For
|
||||
},
|
||||
},
|
||||
scrollToField: (name: NamePath, options: ScrollOptions = {}) => {
|
||||
const namePath = toArray(name);
|
||||
const fieldId = getFieldId(namePath, wrapForm.__INTERNAL__.name);
|
||||
const node: HTMLElement | null = fieldId ? document.getElementById(fieldId) : null;
|
||||
const node = getFieldDOMNode(name, wrapForm);
|
||||
|
||||
if (node) {
|
||||
scrollIntoView(node, {
|
||||
|
@ -47,6 +47,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
|
||||
<code src="./demo/validate-static.tsx">Customized Validation</code>
|
||||
<code src="./demo/dynamic-rule.tsx">Dynamic Rules</code>
|
||||
<code src="./demo/form-dependencies.tsx">Dependencies</code>
|
||||
<code src="./demo/validate-scroll-to-field.tsx" iframe="360">Slide to error field</code>
|
||||
<code src="./demo/validate-other.tsx">Other Form Controls</code>
|
||||
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>
|
||||
<code src="./demo/label-debug.tsx" debug>label ellipsis</code>
|
||||
@ -539,7 +540,7 @@ type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
||||
| pattern | Regex pattern | RegExp | |
|
||||
| required | Required field | boolean | |
|
||||
| transform | Transform value to the rule before validation | (value) => any | |
|
||||
| type | Normally `string` \|`number` \|`boolean` \|`url` \| `email`. More type to ref [here](https://github.com/yiminghe/async-validator#type) | string | |
|
||||
| type | Normally `string` \|`number` \|`boolean` \|`url` \| `email`. More type to ref [here](https://github.com/react-component/async-validator#type) | string | |
|
||||
| validateTrigger | Set validate trigger event. Must be the sub set of `validateTrigger` in Form.Item | string \| string\[] | |
|
||||
| validator | Customize validation rule. Accept Promise as return. See [example](#components-form-demo-register) | ([rule](#rule), value) => Promise | |
|
||||
| warningOnly | Warning only. Not block form submit | boolean | 4.17.0 |
|
||||
@ -666,6 +667,8 @@ React can not get correct interaction of controlled component with async value u
|
||||
|
||||
See similar issues: [#28370](https://github.com/ant-design/ant-design/issues/28370) [#27994](https://github.com/ant-design/ant-design/issues/27994)
|
||||
|
||||
Starting from version `5.17.0`, the sliding operation will prioritize using the ref element forwarded by the form control elements. Therefore, when considering custom components to support verification scrolling, please consider forwarding it to the form control elements first.
|
||||
|
||||
`scrollToFirstError` and `scrollToField` deps on `id` attribute passed to form control, please make sure that it hasn't been ignored in your custom form control. Check [codesandbox](https://codesandbox.io/s/antd-reproduction-template-forked-25nul?file=/index.js) for solution.
|
||||
|
||||
2. multiple forms on same page
|
||||
|
@ -48,6 +48,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*ylFATY6w-ygAAA
|
||||
<code src="./demo/validate-static.tsx">自定义校验</code>
|
||||
<code src="./demo/dynamic-rule.tsx">动态校验规则</code>
|
||||
<code src="./demo/form-dependencies.tsx">校验与更新依赖</code>
|
||||
<code src="./demo/validate-scroll-to-field.tsx" iframe="360">滑动到错误字段</code>
|
||||
<code src="./demo/validate-other.tsx">校验其他组件</code>
|
||||
<code src="./demo/disabled-input-debug.tsx" debug>Disabled Input Debug</code>
|
||||
<code src="./demo/label-debug.tsx" debug>测试 label 省略</code>
|
||||
@ -538,7 +539,7 @@ type Rule = RuleConfig | ((form: FormInstance) => RuleConfig);
|
||||
| pattern | 正则表达式匹配 | RegExp | |
|
||||
| required | 是否为必选字段 | boolean | |
|
||||
| transform | 将字段值转换成目标值后进行校验 | (value) => any | |
|
||||
| type | 类型,常见有 `string` \|`number` \|`boolean` \|`url` \| `email`。更多请参考[此处](https://github.com/yiminghe/async-validator#type) | string | |
|
||||
| type | 类型,常见有 `string` \|`number` \|`boolean` \|`url` \| `email`。更多请参考[此处](https://github.com/react-component/async-validator#type) | string | |
|
||||
| validateTrigger | 设置触发验证时机,必须是 Form.Item 的 `validateTrigger` 的子集 | string \| string\[] | |
|
||||
| validator | 自定义校验,接收 Promise 作为返回值。[示例](#components-form-demo-register)参考 | ([rule](#rule), value) => Promise | |
|
||||
| warningOnly | 仅警告,不阻塞表单提交 | boolean | 4.17.0 |
|
||||
@ -665,6 +666,8 @@ React 中异步更新会导致受控组件交互行为异常。当用户交互
|
||||
|
||||
类似问题:[#28370](https://github.com/ant-design/ant-design/issues/28370) [#27994](https://github.com/ant-design/ant-design/issues/27994)
|
||||
|
||||
从 `5.17.0` 版本开始,滑动操作将优先使用表单控件元素所转发的 ref 元素。因此,在考虑自定义组件支持校验滚动时,请优先考虑将其转发给表单控件元素。
|
||||
|
||||
滚动依赖于表单控件元素上绑定的 `id` 字段,如果自定义控件没有将 `id` 赋到正确的元素上,这个功能将失效。你可以参考这个 [codesandbox](https://codesandbox.io/s/antd-reproduction-template-forked-25nul?file=/index.js)。
|
||||
|
||||
2. 页面内有多个表单
|
||||
|
@ -45,6 +45,11 @@ export interface ComponentToken {
|
||||
* @descEN Form item margin bottom
|
||||
*/
|
||||
itemMarginBottom: number;
|
||||
/**
|
||||
* @desc 行内布局表单项间距
|
||||
* @descEN Inline layout form item margin bottom
|
||||
*/
|
||||
inlineItemMarginBottom: number;
|
||||
/**
|
||||
* @desc 垂直布局标签内边距
|
||||
* @descEN Vertical layout label padding
|
||||
@ -410,7 +415,7 @@ const genHorizontalStyle: GenerateStyle<FormToken> = (token) => {
|
||||
};
|
||||
|
||||
const genInlineStyle: GenerateStyle<FormToken> = (token) => {
|
||||
const { componentCls, formItemCls } = token;
|
||||
const { componentCls, formItemCls, inlineItemMarginBottom } = token;
|
||||
|
||||
return {
|
||||
[`${componentCls}-inline`]: {
|
||||
@ -420,7 +425,7 @@ const genInlineStyle: GenerateStyle<FormToken> = (token) => {
|
||||
[formItemCls]: {
|
||||
flex: 'none',
|
||||
marginInlineEnd: token.margin,
|
||||
marginBottom: 0,
|
||||
marginBottom: inlineItemMarginBottom,
|
||||
|
||||
'&-row': {
|
||||
flexWrap: 'nowrap',
|
||||
@ -552,6 +557,7 @@ export const prepareComponentToken: GetDefaultToken<'Form'> = (token) => ({
|
||||
itemMarginBottom: token.marginLG,
|
||||
verticalLabelPadding: `0 0 ${token.paddingXS}px`,
|
||||
verticalLabelMargin: 0,
|
||||
inlineItemMarginBottom: 0,
|
||||
});
|
||||
|
||||
export const prepareToken: (
|
||||
|
@ -9,10 +9,14 @@ export interface OTPInputProps extends Omit<InputProps, 'onChange'> {
|
||||
onChange: (index: number, value: string) => void;
|
||||
/** Tell parent to do active offset */
|
||||
onActiveChange: (nextIndex: number) => void;
|
||||
|
||||
mask?: boolean | string;
|
||||
}
|
||||
|
||||
const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
||||
const { value, onChange, onActiveChange, index, ...restProps } = props;
|
||||
const { value, onChange, onActiveChange, index, mask, ...restProps } = props;
|
||||
|
||||
const internalValue = value && typeof mask === 'string' ? mask : value;
|
||||
|
||||
const onInternalChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(index, e.target.value);
|
||||
@ -56,13 +60,14 @@ const OTPInput = React.forwardRef<InputRef, OTPInputProps>((props, ref) => {
|
||||
<Input
|
||||
{...restProps}
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
value={internalValue}
|
||||
onInput={onInternalChange}
|
||||
onFocus={syncSelection}
|
||||
onKeyDown={onInternalKeyDown}
|
||||
onKeyUp={onInternalKeyUp}
|
||||
onMouseDown={syncSelection}
|
||||
onMouseUp={syncSelection}
|
||||
type={mask === true ? 'password' : 'text'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -5,11 +5,13 @@ import pickAttrs from 'rc-util/lib/pickAttrs';
|
||||
|
||||
import { getMergedStatus } from '../../_util/statusUtils';
|
||||
import type { InputStatus } from '../../_util/statusUtils';
|
||||
import { devUseWarning } from '../../_util/warning';
|
||||
import { ConfigContext } from '../../config-provider';
|
||||
import useCSSVarCls from '../../config-provider/hooks/useCSSVarCls';
|
||||
import useSize from '../../config-provider/hooks/useSize';
|
||||
import type { SizeType } from '../../config-provider/SizeContext';
|
||||
import { FormItemInputContext } from '../../form/context';
|
||||
import type { FormItemStatusContextProps } from '../../form/context';
|
||||
import type { Variant } from '../../form/hooks/useVariants';
|
||||
import type { InputRef } from '../Input';
|
||||
import useStyle from '../style/otp';
|
||||
@ -42,6 +44,8 @@ export interface OTPProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'on
|
||||
// Status
|
||||
disabled?: boolean;
|
||||
status?: InputStatus;
|
||||
|
||||
mask?: boolean | string;
|
||||
}
|
||||
|
||||
function strToArr(str: string) {
|
||||
@ -61,9 +65,19 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
disabled,
|
||||
status: customStatus,
|
||||
autoFocus,
|
||||
mask,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Input.OTP');
|
||||
warning(
|
||||
!(typeof mask === 'string' && mask.length > 1),
|
||||
'usage',
|
||||
'`mask` prop should be a single character.',
|
||||
);
|
||||
}
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('otp', customizePrefixCls);
|
||||
|
||||
@ -85,7 +99,7 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
const formContext = React.useContext(FormItemInputContext);
|
||||
const mergedStatus = getMergedStatus(formContext.status, customStatus);
|
||||
|
||||
const proxyFormContext = React.useMemo(
|
||||
const proxyFormContext = React.useMemo<FormItemStatusContextProps>(
|
||||
() => ({
|
||||
...formContext,
|
||||
status: mergedStatus,
|
||||
@ -194,10 +208,11 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
};
|
||||
|
||||
// ======================== Render ========================
|
||||
const inputSharedProps = {
|
||||
const inputSharedProps: Partial<OTPInputProps> = {
|
||||
variant,
|
||||
disabled,
|
||||
status: mergedStatus as InputStatus,
|
||||
mask,
|
||||
};
|
||||
|
||||
return wrapCSSVar(
|
||||
@ -216,10 +231,9 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
|
||||
)}
|
||||
>
|
||||
<FormItemInputContext.Provider value={proxyFormContext}>
|
||||
{new Array(length).fill(0).map((_, index) => {
|
||||
{Array.from({ length }).map((_, index) => {
|
||||
const key = `otp-${index}`;
|
||||
const singleValue = valueCells[index] || '';
|
||||
|
||||
return (
|
||||
<OTPInput
|
||||
ref={(inputEle) => {
|
||||
|
@ -35,7 +35,13 @@ const actionMap: Record<PropertyKey, keyof React.DOMAttributes<HTMLSpanElement>>
|
||||
type IconPropsType = React.HTMLAttributes<HTMLSpanElement> & React.Attributes;
|
||||
|
||||
const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
const { visibilityToggle = true } = props;
|
||||
const {
|
||||
disabled,
|
||||
action = 'click',
|
||||
visibilityToggle = true,
|
||||
iconRender = defaultIconRender,
|
||||
} = props;
|
||||
|
||||
const visibilityControlled =
|
||||
typeof visibilityToggle === 'object' && visibilityToggle.visible !== undefined;
|
||||
const [visible, setVisible] = useState(() =>
|
||||
@ -53,7 +59,6 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
const removePasswordTimeout = useRemovePasswordTimeout(inputRef);
|
||||
|
||||
const onVisibleChange = () => {
|
||||
const { disabled } = props;
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
@ -70,7 +75,6 @@ const Password = React.forwardRef<InputRef, PasswordProps>((props, ref) => {
|
||||
};
|
||||
|
||||
const getIcon = (prefixCls: string) => {
|
||||
const { action = 'click', iconRender = defaultIconRender } = props;
|
||||
const iconTrigger = actionMap[action] || '';
|
||||
const icon = iconRender(visible);
|
||||
const iconProps: IconPropsType = {
|
||||
|
@ -10116,20 +10116,13 @@ exports[`renders components/input/demo/group.tsx extend context correctly 2`] =
|
||||
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-vertical ant-space-gap-row-small ant-space-gap-col-small"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
class="ant-flex ant-flex-align-flex-start ant-flex-gap-middle ant-flex-vertical"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With formatter (Upcase)
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
@ -10170,19 +10163,11 @@ exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Disabled
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
@ -10229,19 +10214,11 @@ exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With Length (8)
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
@ -10294,19 +10271,11 @@ exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With variant
|
||||
</h5>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
@ -10347,11 +10316,59 @@ exports[`renders components/input/demo/otp.tsx extend context correctly 1`] = `
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<h5
|
||||
class="ant-typography"
|
||||
>
|
||||
With custom display character
|
||||
</h5>
|
||||
<div
|
||||
class="ant-otp"
|
||||
>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<input
|
||||
class="ant-input ant-input-outlined ant-otp-input"
|
||||
size="1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 2`] = `[]`;
|
||||
exports[`renders components/input/demo/otp.tsx extend context correctly 2`] = `
|
||||
[
|
||||
"Warning: [antd: Input.OTP] \`mask\` prop should be a single character.",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders components/input/demo/password-input.tsx extend context correctly 1`] = `
|
||||
<div
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user