Compare commits

..

15 Commits

Author SHA1 Message Date
lijianan
b32b2dc594 refactor: use genNoMotionStyle (#56999) 2026-02-14 17:53:39 +08:00
github-actions[bot]
90caca416c chore: auto merge branches (#56997)
chore: sync master into feature
2026-02-14 06:04:46 +00:00
thinkasany
95f032bc3d refactor: turn on react-naming-convention/use-state (#56998)
Co-authored-by: 遇见同学 <1875694521@qq.com>
2026-02-14 11:50:16 +08:00
lijianan
5dfaf14db7 feat(a11y): apply prefers-reduced-motion handling for transitions (#56902)
* up

* up

* update

* update

---------

Co-authored-by: 遇见同学 <1875694521@qq.com>
2026-02-14 11:14:18 +08:00
EmilyyyLiu
cd3f333892 feat(Form): add labelAlign prop support in ConfigProvider (#56979)
* feat(Form): add labelAlign prop support in ConfigProvider and Form components

* feat(ConfigProvider): update labelAlign prop support in Form component documentation

* test: Delete unnecessary labelAlign single tests

---------

Co-authored-by: Clayton <118192227+claytonlin1110@users.noreply.github.com>
Co-authored-by: 刘欢 <lh01217311@antgroup.com>
2026-02-13 17:27:37 +08:00
Guo Yunhe
c9a727c599 feat(Cascader,ConfigProvider): support searchIcon, clearIcon, removeIcon, suffixIcon config (#56725)
* feat(Cascader): add support for custom clearIcon and fallback logic

* feat(Cascader): add support for custom removeIcon and enhance icon handling

* feat(Cascader): add searchIcon support and enhance icon configuration

* feat(Cascader): add support for custom searchIcon and enhance configuration options

* feat(Cascader): update searchIcon handling to support configuration through showSearch prop

* feat(Cascader): enhance common props to include searchIcon, clearIcon, removeIcon, and suffixIcon support

* fix(useIcons): change clearIcon and contextClearIcon types from RenderNode to React.ReactNode

* fix(Cascader): update searchIcon handling to ensure proper configuration when showSearch is an object

* refactor(useIcons): replace default icon variables with inline JSX for clearer icon handling

* chore: useMemo

* update version

---------

Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: thinkasany <480968828@qq.com>
2026-02-13 14:25:05 +08:00
github-actions[bot]
2c2773a8cc chore: auto merge branches (#56981)
chore: sync master into feature
2026-02-13 06:15:06 +00:00
NeedmeFordev
4490fc6a4e feat(Splitter): add smooth transition animation for collapsible panels (#56814)
* feat(Splitter): add smooth transition animation for collapsible panels

* fix: clear timeout on rapid clicks and sync with motionDurationMid token

* fix: add initial value to useRef for TypeScript strict mode

* perf: inline collapse animation logic to reduce bundle size

* fix: use token.motionDurationMid instead of hard-coded duration for collapse animation

* feat: add splitter collapse animation option

* refactor: change collapseAnimation to collapseDuration (number | boolean)

* test: add coverage for Panel style default value

* perf: optimize bundle size by inlining functions and simplifying logic

* fix: address code review feedback for collapse animation

* Ensure 100% code coverage percent

* Fix the lint test for splitter

* ensure Splitter collapsible variable

* Ensure the code coverage percent

* Fix Splitter collapse functionality

* Update components/splitter/index.zh-CN.md

Co-authored-by: afc163 <afc163@gmail.com>
Signed-off-by: NeedmeFordev <124189514+spider-yamet@users.noreply.github.com>

* feat: Simplify the logic

* feat: add toggle for motion in collapsible splitter demo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: clean up

* refactor: no recording

* chore: rm AI uselsss test

* chore: clean up

* test: update test case

* test: clean up

---------

Signed-off-by: NeedmeFordev <124189514+spider-yamet@users.noreply.github.com>
Co-authored-by: 遇见同学 <1875694521@qq.com>
Co-authored-by: Wanpan <wanpan96@163.com>
Co-authored-by: thinkasany <480968828@qq.com>
Co-authored-by: afc163 <afc163@gmail.com>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: lijianan <574980606@qq.com>
2026-02-12 21:15:54 +08:00
github-actions[bot]
77fecc07ca chore: auto merge branches (#56959)
chore: feature merge master
2026-02-12 05:06:52 +00:00
lijianan
0088e0221c feat: App should support ref (#56951)
* feat: App should support ref

* update

* update

* add test case

* update test case

* test: improve App warning and Ref test cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: simplify JSX props spread in App

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: 二货机器人 <smith3816@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:41:55 +08:00
github-actions[bot]
88bd8f6de8 chore: auto merge branches (#56953)
chore: sync master into feature
2026-02-11 17:03:59 +00:00
github-actions[bot]
5d3c93bdd6 chore: auto merge branches (#56929)
chore: sync master into feature
2026-02-10 12:46:22 +00:00
二货爱吃白萝卜
61729477bb feat: Space.Addon supprot design token (#56915)
* feat(Space.Addon): add ComponentToken interface and init function

- Create token.ts with 6 customizable tokens
- Add paddingInline, paddingInlineSM, borderRadius, borderRadiusSM, borderRadiusLG, lineWidth
- Include JSDoc comments with Chinese and English descriptions

* feat(Space.Addon): consume component tokens in addon styles

- Update addon.ts to use ComponentToken from token.ts
- Replace global token usage with component tokens (paddingSM -> paddingInline, etc.)
- Register with genStyleHooks using 'Addon' and initComponentToken

* feat(theme): register Addon component in ComponentTokenMap

- Add AddonComponentToken import
- Add Addon entry to ComponentTokenMap interface

* docs(Space.Addon): add demo, tests and documentation for theme customization

- Add component-token.tsx demo showing ConfigProvider usage
- Add demo descriptions in English and Chinese
- Add demo references to index.en-US.md and index.zh-CN.md
- Add test for Addon component token in theme.test.tsx

* fix(Space.Addon): correct type signature for prepareComponentToken

- Use GetDefaultToken<'Addon'> type instead of (token: FullToken<'Addon'>) => ComponentToken
- Import GetDefaultToken from theme/internal
- Update function name from initComponentToken to prepareComponentToken
- Update genStyleHooks call to use prepareComponentToken

* test(Space.Addon): update snapshots for new component-token demo

* test(Space.Addon): use toHaveStyle to check CSS variable

- Add cssVar: true to enable CSS variable generation for Addon tokens
- Use toHaveStyle to verify --ant-addon-padding-inline CSS variable
- Follow the pattern from Select component token test

* docs(Space.Addon): merge zh-CN and en-US into single demo md file

- Remove redundant component-token.zh-CN.md
- Update component-token.md to include both zh-CN and en-US sections
- Follow the pattern used by other demo md files

* refactor(Space.Addon): remove component token system

Remove the ComponentToken interface and prepareComponentToken function as they are no longer needed for the Addon component styling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(Space.Addon): use Space component tokens instead of separate token system

Register Addon as a subcomponent of Space and directly use Space's padding tokens instead of maintaining a separate component token system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(Space.Addon): mark component-token demo as debug

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: update Space demo snapshots

* test(Space.Addon): update snapshot for component-token demo simplification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ConfigProvider): test should use Addon component token instead of Space

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 14:33:03 +08:00
二货爱吃白萝卜
fdb0d6ca6e fix: adjust Select option active style priority higher than selected (#56924) 2026-02-10 14:32:53 +08:00
github-actions[bot]
e93868198b chore: auto merge branches (#56914)
merge feature into master
2026-02-09 09:54:16 +00:00
59 changed files with 597 additions and 193 deletions

View File

@@ -29,13 +29,13 @@ const VersionUpgradeModal = () => {
const [locale, lang] = useLocale(locales);
const { pathname } = useLocation();
const [open, updateOpen] = React.useState(false);
const [open, setOpen] = React.useState(false);
const isCN = lang === 'cn' || utils.isZhCN(pathname);
function handleClose() {
localStorage.setItem(STORAGE_KEY, Date.now().toString());
updateOpen(false);
setOpen(false);
}
React.useEffect(() => {
@@ -48,7 +48,7 @@ const VersionUpgradeModal = () => {
if (!lastTime) {
const timer = setTimeout(() => {
updateOpen(true);
setOpen(true);
}, 1000);
return () => {

View File

@@ -0,0 +1,12 @@
/**
* Search for the first non-undefined value in the arguments and return it.
*
* ```js
* const mergedIcon = fallbackProp(propIcon, contextIcon, defaultIcon);
* ```
*
* Note: it is different from `??` operator which skips null
*/
export default function fallbackProp<T>(...args: T[]): T | undefined {
return args.find((arg) => arg !== undefined);
}

View File

@@ -34,7 +34,7 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
const [varName] = genCssVar(rootPrefixCls, 'wave');
// ===================== Effect =====================
const [color, setWaveColor] = React.useState<string | null>(null);
const [waveColor, setWaveColor] = React.useState<string | null>(null);
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
const [left, setLeft] = React.useState(0);
const [top, setTop] = React.useState(0);
@@ -50,8 +50,8 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
borderRadius: borderRadius.map((radius) => `${radius}px`).join(' '),
};
if (color) {
waveStyle[varName('color')] = color;
if (waveColor) {
waveStyle[varName('color')] = waveColor;
}
function syncPos() {

View File

@@ -21,7 +21,7 @@ export interface AppProps<P = AnyObject> extends AppConfig {
component?: CustomComponent<P> | false;
}
const App: React.FC<AppProps> = (props) => {
const App = React.forwardRef<HTMLElement, AppProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
children,
@@ -79,6 +79,12 @@ const App: React.FC<AppProps> = (props) => {
'When using cssVar, ensure `component` is assigned a valid React component string.',
);
devUseWarning('App')(
!ref || component !== false,
'usage',
'`ref` is not supported when `component` is `false`. Please provide a valid `component` instead.',
);
// ============================ Render ============================
const Component = component === false ? React.Fragment : component;
@@ -90,7 +96,7 @@ const App: React.FC<AppProps> = (props) => {
return (
<AppContext.Provider value={memoizedContextValue}>
<AppConfigContext.Provider value={mergedAppConfig}>
<Component {...(component === false ? undefined : rootProps)}>
<Component {...(component === false ? undefined : { ...rootProps, ref })}>
{ModalContextHolder}
{messageContextHolder}
{notificationContextHolder}
@@ -99,7 +105,7 @@ const App: React.FC<AppProps> = (props) => {
</AppConfigContext.Provider>
</AppContext.Provider>
);
};
});
if (process.env.NODE_ENV !== 'production') {
App.displayName = 'App';

View File

@@ -247,5 +247,20 @@ describe('App', () => {
'Warning: [antd: App] When using cssVar, ensure `component` is assigned a valid React component string.',
);
});
it('should warn if component is false and ref is not empty', () => {
const domRef = React.createRef<HTMLSpanElement>();
render(<App ref={domRef} component={false} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: App] `ref` is not supported when `component` is `false`. Please provide a valid `component` instead.',
);
});
it('App should support Ref', () => {
const domRef = React.createRef<HTMLSpanElement>();
const { container } = render(<App ref={domRef} className="bamboo" component="span" />);
expect(domRef.current).toBe(container.querySelector('.bamboo'));
});
});
});

View File

@@ -225,7 +225,7 @@ const InternalCompoundedButton = React.forwardRef<
const loadingOrDelay = useMemo<LoadingConfigType>(() => getLoadingConfig(loading), [loading]);
const [innerLoading, setLoading] = useState<boolean>(loadingOrDelay.loading);
const [innerLoading, setInnerLoading] = useState<boolean>(loadingOrDelay.loading);
const [hasTwoCNChar, setHasTwoCNChar] = useState<boolean>(false);
@@ -255,10 +255,10 @@ const InternalCompoundedButton = React.forwardRef<
if (loadingOrDelay.delay > 0) {
delayTimer = setTimeout(() => {
delayTimer = null;
setLoading(true);
setInnerLoading(true);
}, loadingOrDelay.delay);
} else {
setLoading(loadingOrDelay.loading);
setInnerLoading(loadingOrDelay.loading);
}
function cleanupTimer() {

View File

@@ -2,6 +2,7 @@ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { unit } from '@ant-design/cssinjs';
import { genFocusStyle, resetIcon } from '../../style';
import { genNoMotionStyle } from '../../style/motion';
import type { GenerateStyle } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
import genGroupStyle from './group';
@@ -40,7 +41,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
userSelect: 'none',
touchAction: 'manipulation',
...genNoMotionStyle(),
'&:disabled > *': {
pointerEvents: 'none',
},
@@ -80,7 +81,7 @@ const genSharedButtonStyle: GenerateStyle<ButtonToken, CSSObject> = (token): CSS
[`${componentCls}-loading-icon`]: {
transition: ['width', 'opacity', 'margin']
.map((transition) => `${transition} ${motionDurationSlow} ${motionEaseInOut}`)
.map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`)
.join(','),
},

View File

@@ -99,7 +99,7 @@ const App: React.FC = () => {
const { styles } = useStyle({ test: true });
const [selectDate, setSelectDate] = React.useState<Dayjs>(() => dayjs());
const [panelDateDate, setPanelDate] = React.useState<Dayjs>(() => dayjs());
const [panelDate, setPanelDate] = React.useState<Dayjs>(() => dayjs());
const onPanelChange = (value: Dayjs, mode: CalendarProps<Dayjs>['mode']) => {
console.log(value.format('YYYY-MM-DD'), mode);
@@ -131,7 +131,7 @@ const App: React.FC = () => {
<span
className={clsx({
[styles.weekend]: isWeekend,
gray: !panelDateDate.isSame(date, 'month'),
gray: !panelDate.isSame(date, 'month'),
})}
>
{date.get('date')}

View File

@@ -907,4 +907,115 @@ describe('Cascader', () => {
expect(screen.getAllByText('bamboo').length).toBe(1);
});
});
describe('clearIcon', () => {
it('should support custom clearIcon', () => {
render(
<Cascader
open
allowClear={{ clearIcon: <div>bamboo</div> }}
options={options}
defaultValue={['zhejiang', 'hangzhou']}
/>,
);
expect(screen.getAllByText('bamboo').length).toBe(1);
});
it('should support ConfigProvider clearIcon', () => {
render(
<ConfigProvider cascader={{ clearIcon: <div>foobar</div> }}>
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} allowClear />
</ConfigProvider>,
);
expect(screen.getAllByText('foobar').length).toBe(1);
});
it('should prefer prop clearIcon over ConfigProvider clearIcon', () => {
render(
<ConfigProvider cascader={{ clearIcon: <div>foobar</div> }}>
<Cascader
allowClear={{ clearIcon: <div>bamboo</div> }}
options={options}
defaultValue={['zhejiang', 'hangzhou']}
/>
</ConfigProvider>,
);
expect(screen.getAllByText('bamboo').length).toBe(1);
});
});
describe('removeIcon', () => {
it('should support custom removeIcon', () => {
render(
<Cascader
multiple
removeIcon={<div>bamboo</div>}
options={options}
defaultValue={[
['zhejiang', 'hangzhou'],
['jiangsu', 'nanjing'],
]}
/>,
);
expect(screen.getAllByText('bamboo').length).toBe(2);
});
it('should support ConfigProvider removeIcon', () => {
render(
<ConfigProvider cascader={{ removeIcon: <div>foobar</div> }}>
<Cascader
multiple
options={options}
defaultValue={[
['zhejiang', 'hangzhou'],
['jiangsu', 'nanjing'],
]}
/>
</ConfigProvider>,
);
expect(screen.getAllByText('foobar').length).toBe(2);
});
it('should prefer prop removeIcon over ConfigProvider removeIcon', () => {
render(
<ConfigProvider cascader={{ removeIcon: <div>foobar</div> }}>
<Cascader
multiple
options={options}
defaultValue={[
['zhejiang', 'hangzhou'],
['jiangsu', 'nanjing'],
]}
removeIcon={<div>bamboo</div>}
/>
</ConfigProvider>,
);
expect(screen.getAllByText('bamboo').length).toBe(2);
});
});
describe('searchIcon', () => {
it('should support custom searchIcon', () => {
render(<Cascader open showSearch={{ searchIcon: <div>bamboo</div> }} options={options} />);
expect(screen.getAllByText('bamboo').length).toBe(1);
});
it('should support ConfigProvider searchIcon', () => {
render(
<ConfigProvider cascader={{ searchIcon: <div>foobar</div> }}>
<Cascader open options={options} showSearch />
</ConfigProvider>,
);
expect(screen.getAllByText('foobar').length).toBe(1);
});
it('should prefer prop searchIcon over ConfigProvider searchIcon', () => {
render(
<ConfigProvider cascader={{ searchIcon: <div>foobar</div> }}>
<Cascader open showSearch={{ searchIcon: <div>bamboo</div> }} options={options} />
</ConfigProvider>,
);
expect(screen.getAllByText('bamboo').length).toBe(1);
});
});
});

View File

@@ -44,12 +44,12 @@ const options: Option[] = [
];
const App: React.FC = () => {
const [placement, SetPlacement] = useState<'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'>(
const [placement, setPlacement] = useState<'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'>(
'topLeft',
);
const placementChange = (e: RadioChangeEvent) => {
SetPlacement(e.target.value);
setPlacement(e.target.value);
};
return (

View File

@@ -111,6 +111,7 @@ Common props ref[Common props](/docs/react/common-props)
| sort | Used to sort filtered options | function(a, b, inputValue) | - | |
| searchValue | Set search value, Need work with `showSearch` | string | - | 4.17.0 |
| onSearch | The callback function triggered when input changed | (search: string) => void | - | 4.17.0 |
| searchIcon | Customize the search icon | ReactNode | - | 6.3.0 |
### Option

View File

@@ -166,6 +166,9 @@ export interface CascaderProps<
bordered?: boolean;
placement?: SelectCommonPlacement;
suffixIcon?: React.ReactNode;
showSearch?:
| boolean
| (SearchConfig<OptionType, keyof OptionType> & { searchIcon?: React.ReactNode });
options?: OptionType[];
status?: InputStatus;
@@ -244,11 +247,12 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
styles,
classNames,
loadingIcon,
...rest
clearIcon,
removeIcon,
suffixIcon,
...restProps
} = props;
const restProps = omit(rest, ['suffixIcon']);
const {
getPrefixCls,
getPopupContainer: getContextPopupContainer,
@@ -258,6 +262,10 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
styles: contextStyles,
expandIcon: contextExpandIcon,
loadingIcon: contextLoadingIcon,
clearIcon: contextClearIcon,
removeIcon: contextRemoveIcon,
suffixIcon: contextSuffixIcon,
searchIcon: contextSearchIcon,
} = useComponentConfig('cascader');
const { popupOverflow } = React.useContext(ConfigContext);
@@ -367,9 +375,21 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
// ===================== Icons =====================
const showSuffixIcon = useShowArrow(props.suffixIcon, showArrow);
const { suffixIcon, removeIcon, clearIcon } = useSelectIcons({
const {
suffixIcon: mergedSuffixIcon,
removeIcon: mergedRemoveIcon,
clearIcon: mergedClearIcon,
} = useSelectIcons({
...props,
clearIcon,
contextClearIcon,
removeIcon,
contextRemoveIcon,
loadingIcon: mergedLoadingIcon,
suffixIcon,
contextSuffixIcon,
searchIcon: typeof showSearch === 'object' && showSearch ? showSearch.searchIcon : undefined,
contextSearchIcon,
hasFeedback,
feedbackIcon,
showSuffixIcon,
@@ -386,7 +406,7 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
return isRtl ? 'bottomRight' : 'bottomLeft';
}, [placement, isRtl]);
const mergedAllowClear = allowClear === true ? { clearIcon } : allowClear;
const mergedAllowClear = allowClear === true ? { clearIcon: mergedClearIcon } : allowClear;
// =========== Merged Props for Semantic ==========
const mergedProps: CascaderProps<any> = {
@@ -468,8 +488,8 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
allowClear={mergedAllowClear}
showSearch={mergedShowSearch}
expandIcon={mergedExpandIcon}
suffixIcon={suffixIcon}
removeIcon={removeIcon}
suffixIcon={mergedSuffixIcon}
removeIcon={mergedRemoveIcon}
loadingIcon={mergedLoadingIcon}
checkable={checkable}
popupClassName={mergedPopupClassName}

View File

@@ -114,6 +114,7 @@ demo:
| sort | 用于排序 filter 后的选项 | function(a, b, inputValue) | - | |
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 4.17.0 |
| onSearch | 监听搜索,返回输入的值 | (search: string) => void | - | 4.17.0 |
| searchIcon | 自定义的搜索图标 | ReactNode | - | 6.3.0 |
### Option

View File

@@ -1,6 +1,7 @@
import { unit } from '@ant-design/cssinjs';
import { genFocusOutline, resetComponent } from '../../style';
import { genNoMotionStyle } from '../../style/motion';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
@@ -99,6 +100,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
borderRadius: token.borderRadiusSM,
borderCollapse: 'separate',
transition: `all ${token.motionDurationSlow}`,
...genNoMotionStyle(),
// Checkmark
'&:after': {
@@ -116,6 +118,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
opacity: 0,
content: '""',
transition: `all ${token.motionDurationFast} ${token.motionEaseInBack}, opacity ${token.motionDurationFast}`,
...genNoMotionStyle(),
},
// Wrapper > Checkbox > input
@@ -173,6 +176,7 @@ export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token) => {
opacity: 1,
transform: 'rotate(45deg) scale(1) translate(-50%,-50%)',
transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack} ${token.motionDurationFast}`,
...genNoMotionStyle(),
},
// Hover on checked checkbox directly

View File

@@ -229,6 +229,34 @@ describe('ConfigProvider.Form', () => {
});
});
describe('form labelAlign', () => {
it('set labelAlign left', () => {
const { container } = render(
<ConfigProvider form={{ labelAlign: 'left' }}>
<Form>
<Form.Item label="姓名">
<input />
</Form.Item>
</Form>
</ConfigProvider>,
);
expect(container.querySelector('.ant-form-item-label-left')).toBeTruthy();
});
it('form labelAlign should override ConfigProvider labelAlign', () => {
const { container } = render(
<ConfigProvider form={{ labelAlign: 'left' }}>
<Form labelAlign="right">
<Form.Item label="姓名">
<input />
</Form.Item>
</Form>
</ConfigProvider>,
);
expect(container.querySelector('.ant-form-item-label-left')).toBeFalsy();
});
});
describe('form disabled', () => {
it('set Input enabled', () => {
const { container } = render(

View File

@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { Modal } from 'antd';
import ConfigProvider from '..';
import { Button, InputNumber, Select } from '../..';
import { Button, InputNumber, Select, Space } from '../..';
import { render, waitFakeTimer } from '../../../tests/utils';
import theme from '../../theme';
import type { GlobalToken } from '../../theme/internal';
@@ -113,6 +113,21 @@ describe('ConfigProvider.Theme', () => {
).toBeTruthy();
});
it('should support Addon component token', () => {
const { container } = render(
<ConfigProvider theme={{ components: { Addon: { colorText: '#0000FF', algorithm: true } } }}>
<Space.Compact>
<Space.Addon className="test-addon">Addon Content</Space.Addon>
</Space.Compact>
</ConfigProvider>,
);
const addon = container.querySelector('.test-addon')!;
expect(addon).toHaveStyle({
'--ant-color-text': '#0000FF',
});
});
it('hashed should be true if not changed', () => {
let hashId = 'hashId';

View File

@@ -302,6 +302,7 @@ export type FormConfig = ComponentStyleConfig &
| 'classNames'
| 'styles'
| 'tooltip'
| 'labelAlign'
>;
export type FloatButtonConfig = ComponentStyleConfig &
@@ -372,7 +373,10 @@ export type InputNumberConfig = ComponentStyleConfig &
Pick<InputNumberProps, 'variant' | 'classNames' | 'styles'>;
export type CascaderConfig = ComponentStyleConfig &
Pick<CascaderProps, 'variant' | 'styles' | 'classNames' | 'expandIcon' | 'loadingIcon'>;
Pick<
CascaderProps,
'variant' | 'styles' | 'classNames' | 'expandIcon' | 'loadingIcon' | 'removeIcon' | 'suffixIcon'
> & { clearIcon?: React.ReactNode; searchIcon?: React.ReactNode };
export type TreeSelectConfig = ComponentStyleConfig &
Pick<TreeSelectProps, 'variant' | 'classNames' | 'styles' | 'switcherIcon'>;

View File

@@ -225,11 +225,11 @@ const Page: React.FC = () => {
};
const App: React.FC = () => {
const [locale, setLocal] = useState<Locale>(enUS);
const [locale, setLocale] = useState<Locale>(enUS);
const changeLocale = (e: RadioChangeEvent) => {
const localeValue = e.target.value;
setLocal(localeValue);
setLocale(localeValue);
if (!localeValue) {
dayjs.locale('en');
} else {

View File

@@ -119,7 +119,7 @@ const {
| cardMeta | Set Card.Meta common props | { className?: string, style?: React.CSSProperties, classNames?: [CardMetaProps\["classNames"\]](/components/card#semantic-dom), styles?: [CardMetaProps\["styles"\]](/components/card#semantic-dom) } | - | 6.0.0 |
| calendar | Set Calendar common props | { className?: string, style?: React.CSSProperties, classNames?: [CalendarConfig\["classNames"\]](/components/calendar#semantic-dom), styles?: [CalendarConfig\["styles"\]](/components/calendar#semantic-dom) } | - | 5.7.0, `classNames` and `styles`: 6.0.0 |
| carousel | Set Carousel common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| cascader | Set Cascader common props | { className?: string, style?: React.CSSProperties, classNames?: [CascaderConfig\["classNames"\]](/components/cascader#semantic-dom), styles?: [CascaderConfig\["styles"\]](/components/cascader#semantic-dom), expandIcon?: React.ReactNode, loadingIcon?: React.ReactNode } | - | 5.7.0, `classNames` and `styles`: 6.0.0, `expandIcon` and `loadingIcon`: 6.3.0 |
| cascader | Set Cascader common props | { className?: string, style?: React.CSSProperties, classNames?: [CascaderConfig\["classNames"\]](/components/cascader#semantic-dom), styles?: [CascaderConfig\["styles"\]](/components/cascader#semantic-dom), expandIcon?: React.ReactNode, loadingIcon?: React.ReactNode, searchIcon?: React.ReactNode, clearIcon?: React.ReactNode, removeIcon?: React.ReactNode, suffixIcon?: React.ReactNode } | - | 5.7.0, `classNames` and `styles`: 6.0.0, `expandIcon`, `loadingIcon`, `searchIcon`, `clearIcon`, `removeIcon`, `suffixIcon`: 6.4.0 |
| checkbox | Set Checkbox common props | { className?: string, style?: React.CSSProperties, classNames?: [CheckboxConfig\["classNames"\]](/components/checkbox#semantic-dom), styles?: [CheckboxConfig\["styles"\]](/components/checkbox#semantic-dom) } | - | 5.7.0, `classNames` and `styles`: 6.0.0 |
| collapse | Set Collapse common props | { className?: string, style?: React.CSSProperties, expandIcon?: (props) => ReactNode, classNames?: [CollapseProps\["classNames"\]](/components/collapse#semantic-dom), styles?: [CollapseProps\["styles"\]](/components/collapse#semantic-dom) } | - | 5.7.0, `expandIcon`: 5.15.0, `classNames` and `styles`: 6.0.0 |
| colorPicker | Set ColorPicker common props | { className?: string, style?: React.CSSProperties, classNames?: [ColorPickerConfig\["classNames"\]](/components/color-picker#semantic-dom), styles?: [ColorPickerConfig\["styles"\]](/components/color-picker#semantic-dom) } | - | 5.7.0 |
@@ -133,7 +133,7 @@ const {
| flex | Set Flex common props | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 |
| floatButton | Set FloatButton common props | { className?: string, style?: React.CSSProperties, classNames?: [FloatButtonProps\["classNames"\]](/components/float-button#semantic-dom), styles?: [FloatButtonProps\["styles"\]](/components/float-button#semantic-dom), backTopIcon?: React.ReactNode } | - | |
| floatButtonGroup | Set FloatButton.Group common props | { closeIcon?: React.ReactNode, className?: string, style?: React.CSSProperties, classNames?: [FloatButtonProps\["classNames"\]](/components/float-button#semantic-dom), styles?: [FloatButtonProps\["styles"\]](/components/float-button#semantic-dom) } | - | |
| form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options), classNames?:[FormConfig\["classNames"\]](/components/form#semantic-dom), styles?: [FormConfig\["styles"\]](/components/form#semantic-dom), tooltip?: [TooltipProps](/components/tooltip#api) & { icon?: ReactNode } } | - | `requiredMark`: 4.8.0; `colon`: 4.18.0; `scrollToFirstError`: 5.2.0; `className` and `style`: 5.7.0; `tooltip`: 6.3.0 |
| form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options), classNames?:[FormConfig\["classNames"\]](/components/form#semantic-dom), styles?: [FormConfig\["styles"\]](/components/form#semantic-dom), tooltip?: [TooltipProps](/components/tooltip#api) & { icon?: ReactNode }, labelAlign?: `left` \| `right` } | - | `requiredMark`: 4.8.0; `colon`: 4.18.0; `scrollToFirstError`: 5.2.0; `className` and `style`: 5.7.0; `tooltip`: 6.3.0; `labelAlign`: 6.4.0 |
| image | Set Image common props | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode, classNames?:[ImageConfig\["classNames"\]](/components/image#semantic-dom), styles?: [ImageConfig\["styles"\]](/components/image#semantic-dom) }, fallback?: string } | - | 5.7.0, `closeIcon`: 5.14.0, `classNames` and `styles`: 6.0.0 |
| input | Set Input common props | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 4.2.0, `allowClear`: 5.15.0 |
| inputNumber | Set InputNumber common props | { className?: string, style?: React.CSSProperties, classNames?: [InputNumberConfig\["classNames"\]](/components/input-number#semantic-dom), styles?: [InputNumberConfig\["styles"\]](/components/input-number#semantic-dom) } | - | |

View File

@@ -121,7 +121,7 @@ const {
| card | 设置 Card 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CardProps\["classNames"\]](/components/card-cn#semantic-dom), styles?: [CardProps\["styles"\]](/components/card-cn#semantic-dom) } | - | 5.7.0, `classNames``styles`: 5.14.0 |
| cardMeta | 设置 Card.Meta 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CardMetaProps\["classNames"\]](/components/card-cn#semantic-dom), styles?: [CardMetaProps\["styles"\]](/components/card-cn#semantic-dom) } | - | 6.0.0 |
| carousel | 设置 Carousel 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| cascader | 设置 Cascader 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CascaderConfig\["classNames"\]](/components/cascader#semantic-dom), styles?: [CascaderConfig\["styles"\]](/components/cascader#semantic-dom), expandIcon?: React.ReactNode, loadingIcon?: React.ReactNode } | - | 5.7.0, `classNames``styles`: 6.0.0, `expandIcon` `loadingIcon`: 6.3.0 |
| cascader | 设置 Cascader 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CascaderConfig\["classNames"\]](/components/cascader#semantic-dom), styles?: [CascaderConfig\["styles"\]](/components/cascader#semantic-dom), expandIcon?: React.ReactNode, loadingIcon?: React.ReactNode, searchIcon?: React.ReactNode, clearIcon?: React.ReactNode, removeIcon?: React.ReactNode, suffixIcon?: React.ReactNode } | - | 5.7.0, `classNames``styles`: 6.0.0, `expandIcon`, `loadingIcon`, `searchIcon`, `clearIcon`, `removeIcon`, `suffixIcon`: 6.4.0 |
| checkbox | 设置 Checkbox 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [CheckboxConfig\["classNames"\]](/components/checkbox-cn#semantic-dom), styles?: [CheckboxConfig\["styles"\]](/components/checkbox-cn#semantic-dom) } | - | 5.7.0, `classNames``styles`: 6.0.0 |
| collapse | 设置 Collapse 组件的通用属性 | { className?: string, style?: React.CSSProperties, expandIcon?: (props) => ReactNode, classNames?: [CollapseProps\["classNames"\]](/components/collapse-cn#semantic-dom), styles?: [CollapseProps\["styles"\]](/components/collapse-cn#semantic-dom) } | - | 5.7.0, `expandIcon`: 5.15.0, `classNames``styles`: 6.0.0 |
| colorPicker | 设置 ColorPicker 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [ColorPickerConfig\["classNames"\]](/components/color-picker-cn#semantic-dom), styles?: [ColorPickerConfig\["styles"\]](/components/color-picker-cn#semantic-dom) } | - | 5.7.0 |
@@ -135,7 +135,7 @@ const {
| flex | 设置 Flex 组件的通用属性 | { className?: string, style?: React.CSSProperties, vertical?: boolean } | - | 5.10.0 |
| floatButton | 设置 FloatButton 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [FloatButtonProps\["classNames"\]](/components/float-button-cn#semantic-dom), styles?: [FloatButtonProps\["styles"\]](/components/float-button-cn#semantic-dom), backTopIcon?: React.ReactNode } | - | |
| floatButtonGroup | 设置 FloatButton.Group 组件的通用属性 | { closeIcon?: React.ReactNode, className?: string, style?: React.CSSProperties, classNames?: [FloatButtonProps\["classNames"\]](/components/float-button-cn#semantic-dom), styles?: [FloatButtonProps\["styles"\]](/components/float-button-cn#semantic-dom) } | - | |
| form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options), classNames?:[FormConfig\["classNames"\]](/components/form-cn#semantic-dom), styles?: [FormConfig\["styles"\]](/components/form-cn#semantic-dom), tooltip?: [TooltipProps](/components/tooltip-cn#api) & { icon?: ReactNode } } | - | `requiredMark`: 4.8.0; `colon`: 4.18.0; `scrollToFirstError`: 5.2.0; `className``style`: 5.7.0; `tooltip`: 6.3.0 |
| form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options), classNames?:[FormConfig\["classNames"\]](/components/form-cn#semantic-dom), styles?: [FormConfig\["styles"\]](/components/form-cn#semantic-dom), tooltip?: [TooltipProps](/components/tooltip-cn#api) & { icon?: ReactNode }, labelAlign?: `left` \| `right` } | - | `requiredMark`: 4.8.0; `colon`: 4.18.0; `scrollToFirstError`: 5.2.0; `className``style`: 5.7.0; `tooltip`: 6.3.0; `labelAlign`: 6.4.0 |
| image | 设置 Image 组件的通用属性 | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode, classNames?:[ImageConfig\["classNames"\]](/components/image-cn#semantic-dom), styles?: [ImageConfig\["styles"\]](/components/image-cn#semantic-dom) }, fallback?: string } | - | 5.7.0, `closeIcon`: 5.14.0, `classNames``styles`: 6.0.0 |
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties,classNames?:[InputConfig\["classNames"\]](/components/input-cn#semantic-input), styles?: [InputConfig\["styles"\]](/components/input-cn#semantic-input), allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.7.0, `allowClear`: 5.15.0 |
| inputNumber | 设置 Input 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [InputNumberConfig\["classNames"\]](/components/input-number-cn#semantic-dom), styles?: [InputNumberConfig\["styles"\]](/components/input-number-cn#semantic-dom) } | - | |

View File

@@ -5,10 +5,10 @@ import { DatePicker, Radio } from 'antd';
const { RangePicker } = DatePicker;
const App: React.FC = () => {
const [placement, SetPlacement] = useState<DatePickerProps['placement']>('topLeft');
const [placement, setPlacement] = useState<DatePickerProps['placement']>('topLeft');
const placementChange = (e: RadioChangeEvent) => {
SetPlacement(e.target.value);
setPlacement(e.target.value);
};
return (

View File

@@ -54,8 +54,8 @@ const stylesFn: DrawerProps['styles'] = (info) => {
};
const App: React.FC = () => {
const [drawerOpen, setOpen] = useState(false);
const [drawerFnOpen, setFnOpen] = useState(false);
const [drawerOpen, setDrawerOpen] = useState(false);
const [drawerFnOpen, setDrawerFnOpen] = useState(false);
const sharedProps: DrawerProps = {
classNames,
@@ -65,7 +65,7 @@ const App: React.FC = () => {
const footer: React.ReactNode = (
<Flex gap="middle" justify="flex-end">
<Button
onClick={() => setFnOpen(false)}
onClick={() => setDrawerFnOpen(false)}
styles={{ root: { borderColor: '#ccc', color: '#171717', backgroundColor: '#fff' } }}
>
Cancel
@@ -73,7 +73,7 @@ const App: React.FC = () => {
<Button
type="primary"
styles={{ root: { backgroundColor: '#171717' } }}
onClick={() => setOpen(true)}
onClick={() => setDrawerOpen(true)}
>
Submit
</Button>
@@ -82,8 +82,8 @@ const App: React.FC = () => {
return (
<Flex gap="middle">
<Button onClick={() => setOpen(true)}>Open Style Drawer</Button>
<Button type="primary" onClick={() => setFnOpen(true)}>
<Button onClick={() => setDrawerOpen(true)}>Open Style Drawer</Button>
<Button type="primary" onClick={() => setDrawerFnOpen(true)}>
Open Function Drawer
</Button>
<Drawer
@@ -92,7 +92,7 @@ const App: React.FC = () => {
title="Custom Style Drawer"
styles={styles}
open={drawerOpen}
onClose={() => setOpen(false)}
onClose={() => setDrawerOpen(false)}
>
{sharedContent}
</Drawer>
@@ -103,7 +103,7 @@ const App: React.FC = () => {
styles={stylesFn}
mask={{ enabled: true, blur: true }}
open={drawerFnOpen}
onClose={() => setFnOpen(false)}
onClose={() => setDrawerFnOpen(false)}
>
{sharedContent}
</Drawer>

View File

@@ -91,6 +91,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
styles: contextStyles,
classNames: contextClassNames,
tooltip: contextTooltip,
labelAlign: contextLabelAlign,
} = useComponentConfig('form');
const {
@@ -144,6 +145,8 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
const mergedColon = colon ?? contextColon;
const mergedLabelAlign = labelAlign ?? contextLabelAlign;
const mergedTooltip = { ...contextTooltip, ...tooltip };
const prefixCls = getPrefixCls('form', customizePrefixCls);
@@ -194,7 +197,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
const formContextValue = React.useMemo<FormContextProps>(
() => ({
name,
labelAlign,
labelAlign: mergedLabelAlign,
labelCol,
labelWrap,
wrapperCol,
@@ -210,7 +213,7 @@ const InternalForm: React.ForwardRefRenderFunction<FormRef, FormProps> = (props,
}),
[
name,
labelAlign,
mergedLabelAlign,
labelCol,
wrapperCol,
layout,

View File

@@ -829,13 +829,13 @@ describe('Form', () => {
// https://github.com/ant-design/ant-design/issues/20813
it('should update help directly when provided', async () => {
const App: React.FC = () => {
const [message, updateMessage] = React.useState('');
const [message, setMessage] = React.useState('');
return (
<Form>
<Form.Item label="hello" help={message}>
<Input />
</Form.Item>
<Button onClick={() => updateMessage('bamboo')} />
<Button onClick={() => setMessage('bamboo')} />
</Form>
);
};

View File

@@ -14,10 +14,10 @@ const customizeRequiredMark = (label: React.ReactNode, { required }: { required:
const App: React.FC = () => {
const [form] = Form.useForm();
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional');
const [requiredMark, setRequiredMark] = useState<RequiredMark>('optional');
const onRequiredTypeChange: FormProps<any>['onValuesChange'] = ({ requiredMarkValue }) => {
setRequiredMarkType(requiredMarkValue);
setRequiredMark(requiredMarkValue);
};
return (

View File

@@ -60,8 +60,8 @@ const stylesFn: ModalProps['styles'] = (info) => {
};
const App: React.FC = () => {
const [modalOpen, setOpen] = useState(false);
const [modalFnOpen, setFnOpen] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const [modalFnOpen, setModalFnOpen] = useState(false);
const sharedProps: ModalProps = {
centered: true,
@@ -71,7 +71,7 @@ const App: React.FC = () => {
const footer: React.ReactNode = (
<>
<Button
onClick={() => setFnOpen(false)}
onClick={() => setModalFnOpen(false)}
styles={{ root: { borderColor: '#ccc', color: '#171717', backgroundColor: '#fff' } }}
>
Cancel
@@ -79,7 +79,7 @@ const App: React.FC = () => {
<Button
type="primary"
styles={{ root: { backgroundColor: '#171717' } }}
onClick={() => setOpen(true)}
onClick={() => setModalOpen(true)}
>
Submit
</Button>
@@ -88,8 +88,8 @@ const App: React.FC = () => {
return (
<Flex gap="middle">
<Button onClick={() => setOpen(true)}>Open Style Modal</Button>
<Button type="primary" onClick={() => setFnOpen(true)}>
<Button onClick={() => setModalOpen(true)}>Open Style Modal</Button>
<Button type="primary" onClick={() => setModalFnOpen(true)}>
Open Function Modal
</Button>
<Modal
@@ -98,8 +98,8 @@ const App: React.FC = () => {
title="Custom Style Modal"
styles={styles}
open={modalOpen}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={() => setModalOpen(false)}
onCancel={() => setModalOpen(false)}
>
{sharedContent}
</Modal>
@@ -110,8 +110,8 @@ const App: React.FC = () => {
styles={stylesFn}
mask={{ enabled: true, blur: true }}
open={modalFnOpen}
onOk={() => setFnOpen(false)}
onCancel={() => setFnOpen(false)}
onOk={() => setModalFnOpen(false)}
onCancel={() => setModalFnOpen(false)}
>
{sharedContent}
</Modal>

View File

@@ -15,12 +15,12 @@ const randomOptions = (count?: number) => {
};
const App: React.FC = () => {
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
const [open, setOpen] = useState(false);
const [options, setOptions] = useState(() => randomOptions(3));
const placementChange = (e: RadioChangeEvent) => {
SetPlacement(e.target.value);
setPlacement(e.target.value);
};
return (

View File

@@ -5,10 +5,10 @@ import { Radio, Select } from 'antd';
type SelectCommonPlacement = SelectProps['placement'];
const App: React.FC = () => {
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
const placementChange = (e: RadioChangeEvent) => {
SetPlacement(e.target.value);
setPlacement(e.target.value);
};
return (

View File

@@ -121,10 +121,6 @@ const genSingleStyle: GenerateStyle<SelectToken> = (token) => {
alignItems: 'center',
},
[`&-active:not(${selectItemCls}-option-disabled)`]: {
backgroundColor: token.optionActiveBg,
},
[`&-selected:not(${selectItemCls}-option-disabled)`]: {
color: token.optionSelectedColor,
fontWeight: token.optionSelectedFontWeight,
@@ -135,6 +131,10 @@ const genSingleStyle: GenerateStyle<SelectToken> = (token) => {
},
},
[`&-active:not(${selectItemCls}-option-disabled)`]: {
backgroundColor: token.optionActiveBg,
},
'&-disabled': {
[`&${selectItemCls}-option-selected`]: {
backgroundColor: token.colorBgContainerDisabled,

View File

@@ -7,17 +7,23 @@ import DownOutlined from '@ant-design/icons/DownOutlined';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import fallbackProp from '../_util/fallbackProp';
import { devUseWarning } from '../_util/warning';
type RenderNode = React.ReactNode | ((props: any) => React.ReactNode);
export default function useIcons({
suffixIcon,
contextSuffixIcon,
clearIcon,
contextClearIcon,
menuItemSelectedIcon,
removeIcon,
contextRemoveIcon,
loading,
loadingIcon,
searchIcon,
contextSearchIcon,
multiple,
hasFeedback,
showSuffixIcon,
@@ -26,11 +32,16 @@ export default function useIcons({
componentName,
}: {
suffixIcon?: React.ReactNode;
clearIcon?: RenderNode;
contextSuffixIcon?: React.ReactNode;
clearIcon?: React.ReactNode;
contextClearIcon?: React.ReactNode;
menuItemSelectedIcon?: RenderNode;
removeIcon?: RenderNode;
contextRemoveIcon?: RenderNode;
loading?: boolean;
loadingIcon?: React.ReactNode;
searchIcon?: React.ReactNode;
contextSearchIcon?: React.ReactNode;
multiple?: boolean;
hasFeedback?: boolean;
feedbackIcon?: ReactNode;
@@ -45,59 +56,72 @@ export default function useIcons({
warning.deprecated(!clearIcon, 'clearIcon', 'allowClear={{ clearIcon: React.ReactNode }}');
}
// Clear Icon
const mergedClearIcon = clearIcon ?? <CloseCircleFilled />;
return React.useMemo(() => {
// Clear Icon
const mergedClearIcon = fallbackProp(clearIcon, contextClearIcon, <CloseCircleFilled />);
// Validation Feedback Icon
const getSuffixIconNode = (arrowIcon?: ReactNode) => {
if (suffixIcon === null && !hasFeedback && !showArrow) {
return null;
}
return (
<>
{showSuffixIcon !== false && arrowIcon}
{hasFeedback && feedbackIcon}
</>
);
};
// Arrow item icon
let mergedSuffixIcon = null;
if (suffixIcon !== undefined) {
mergedSuffixIcon = getSuffixIconNode(suffixIcon);
} else if (loading) {
mergedSuffixIcon = getSuffixIconNode(loadingIcon ?? <LoadingOutlined spin />);
} else {
mergedSuffixIcon = ({ open, showSearch }: { open: boolean; showSearch: boolean }) => {
if (open && showSearch) {
return getSuffixIconNode(<SearchOutlined />);
// Validation Feedback Icon
const getSuffixIconNode = (arrowIcon?: ReactNode) => {
if (suffixIcon === null && !hasFeedback && !showArrow) {
return null;
}
return getSuffixIconNode(<DownOutlined />);
return (
<>
{showSuffixIcon !== false && arrowIcon}
{hasFeedback && feedbackIcon}
</>
);
};
}
// Checked item icon
let mergedItemIcon = null;
if (menuItemSelectedIcon !== undefined) {
mergedItemIcon = menuItemSelectedIcon;
} else if (multiple) {
mergedItemIcon = <CheckOutlined />;
} else {
mergedItemIcon = null;
}
// Arrow item icon
let mergedSuffixIcon = null;
if (suffixIcon !== undefined) {
mergedSuffixIcon = getSuffixIconNode(suffixIcon);
} else if (loading) {
mergedSuffixIcon = getSuffixIconNode(fallbackProp(loadingIcon, <LoadingOutlined spin />));
} else {
mergedSuffixIcon = ({ open, showSearch }: { open: boolean; showSearch: boolean }) => {
if (open && showSearch) {
return getSuffixIconNode(fallbackProp(searchIcon, contextSearchIcon, <SearchOutlined />));
}
return getSuffixIconNode(fallbackProp(contextSuffixIcon, <DownOutlined />));
};
}
let mergedRemoveIcon = null;
if (removeIcon !== undefined) {
mergedRemoveIcon = removeIcon;
} else {
mergedRemoveIcon = <CloseOutlined />;
}
// Checked item icon
let mergedItemIcon = null;
if (menuItemSelectedIcon !== undefined) {
mergedItemIcon = menuItemSelectedIcon;
} else if (multiple) {
mergedItemIcon = <CheckOutlined />;
} else {
mergedItemIcon = null;
}
return {
// TODO: remove as when all the deps bumped
clearIcon: mergedClearIcon as React.ReactNode,
suffixIcon: mergedSuffixIcon,
itemIcon: mergedItemIcon,
removeIcon: mergedRemoveIcon,
};
const mergedRemoveIcon = fallbackProp(removeIcon, contextRemoveIcon, <CloseOutlined />);
return {
clearIcon: mergedClearIcon,
suffixIcon: mergedSuffixIcon,
itemIcon: mergedItemIcon,
removeIcon: mergedRemoveIcon,
};
}, [
suffixIcon,
contextSuffixIcon,
clearIcon,
contextClearIcon,
menuItemSelectedIcon,
removeIcon,
contextRemoveIcon,
loading,
loadingIcon,
searchIcon,
contextSearchIcon,
multiple,
hasFeedback,
showSuffixIcon,
feedbackIcon,
showArrow,
]);
}

View File

@@ -16066,6 +16066,28 @@ exports[`renders components/space/demo/compact-nested.tsx extend context correct
exports[`renders components/space/demo/compact-nested.tsx extend context correctly 2`] = `[]`;
exports[`renders components/space/demo/component-token.tsx extend context correctly 1`] = `
<div
class="ant-space-compact"
>
<div
class="ant-space-addon ant-space-addon-compact-item ant-space-addon-compact-first-item css-var-test-id ant-space-addon-variant-outlined"
>
Addon
</div>
<button
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-compact-item ant-btn-compact-last-item"
type="button"
>
<span>
Button
</span>
</button>
</div>
`;
exports[`renders components/space/demo/component-token.tsx extend context correctly 2`] = `[]`;
exports[`renders components/space/demo/debug.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"

View File

@@ -4097,6 +4097,26 @@ exports[`renders components/space/demo/compact-nested.tsx correctly 1`] = `
</div>
`;
exports[`renders components/space/demo/component-token.tsx correctly 1`] = `
<div
class="ant-space-compact"
>
<div
class="ant-space-addon ant-space-addon-compact-item ant-space-addon-compact-first-item css-var-test-id ant-space-addon-variant-outlined"
>
Addon
</div>
<button
class="ant-btn css-var-test-id ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ant-btn-compact-item ant-btn-compact-last-item"
type="button"
>
<span>
Button
</span>
</button>
</div>
`;
exports[`renders components/space/demo/debug.tsx correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center ant-space-gap-row-small ant-space-gap-col-small css-var-test-id"

View File

@@ -0,0 +1,7 @@
## zh-CN
使用 `ConfigProvider` 自定义 `Space.Addon` 的主题样式。
## en-US
Use `ConfigProvider` to customize the theme of `Space.Addon`.

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { Button, ConfigProvider, Space } from 'antd';
const App: React.FC = () => (
<ConfigProvider
theme={{
components: {
Addon: { colorText: 'blue', algorithm: true },
},
}}
>
<Space.Compact>
<Space.Addon>Addon</Space.Addon>
<Button type="primary">Button</Button>
</Space.Compact>
</ConfigProvider>
);
export default App;

View File

@@ -34,6 +34,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
<code src="./demo/debug.tsx" debug>Diverse Child</code>
<code src="./demo/gap-in-line.tsx" debug>Flex gap style</code>
<code src="./demo/style-class.tsx" version="6.0.0">Custom semantic dom styling</code>
<code src="./demo/component-token.tsx" debug>Customize Addon with theme</code>
## API

View File

@@ -38,6 +38,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*37T2R6O9oi0AAA
<code src="./demo/debug.tsx" debug>多样的 Child</code>
<code src="./demo/gap-in-line.tsx" debug>Flex gap 样式</code>
<code src="./demo/style-class.tsx" version="6.0.0">自定义语义结构的样式和类</code>
<code src="./demo/component-token.tsx" debug>自定义主题</code>
## API

View File

@@ -1,3 +1,4 @@
import { resetComponent } from '../../style';
import { genCompactItemStyle } from '../../style/compact-item';
import { genStyleHooks } from '../../theme/internal';
import type { FullToken, GenerateStyle } from '../../theme/internal';
@@ -7,11 +8,11 @@ import { genCssVar } from '../../theme/util/genStyleUtils';
// biome-ignore lint/suspicious/noEmptyInterface: ComponentToken need to be empty by default
export interface ComponentToken {}
interface SpaceToken extends FullToken<'Space'> {
interface AddonToken extends FullToken<'Space'> {
// Custom token here
}
const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
const genSpaceAddonStyle: GenerateStyle<AddonToken> = (token) => {
const {
componentCls,
borderRadius,
@@ -27,7 +28,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
antCls,
} = token;
const [varName, varRef] = genCssVar(antCls, 'space');
const [varName, varRef] = genCssVar(antCls, 'space-addon');
return {
[componentCls]: [
@@ -35,6 +36,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
// == Base ==
// ==========================================================
{
...resetComponent(token),
display: 'inline-flex',
alignItems: 'center',
gap: 0,
@@ -144,7 +146,7 @@ const genSpaceAddonStyle: GenerateStyle<SpaceToken> = (token) => {
};
// ============================== Export ==============================
export default genStyleHooks(['Space', 'Addon'], (token) => [
export default genStyleHooks('Addon', (token) => [
genSpaceAddonStyle(token),
genCompactItemStyle(token, { focus: false }),
]);

View File

@@ -7,11 +7,14 @@ export const InternalPanel = forwardRef<
HTMLDivElement,
React.PropsWithChildren<InternalPanelProps>
>((props, ref) => {
const { prefixCls, className, children, size, style = {} } = props;
const { prefixCls, className, children, size, style = {}, supportMotion } = props;
const panelClassName = clsx(
`${prefixCls}-panel`,
{ [`${prefixCls}-panel-hidden`]: size === 0 },
{
[`${prefixCls}-panel-hidden`]: size === 0,
[`${prefixCls}-panel-transition`]: supportMotion,
},
className,
);

View File

@@ -28,6 +28,7 @@ const Splitter: React.FC<React.PropsWithChildren<SplitterProps>> = (props) => {
prefixCls: customizePrefixCls,
className,
classNames,
collapsible,
style,
styles,
layout,
@@ -217,9 +218,13 @@ const Splitter: React.FC<React.PropsWithChildren<SplitterProps>> = (props) => {
style: { ...mergedStyles.panel, ...item.style },
};
// Panel
const panel = (
<InternalPanel {...panelProps} prefixCls={prefixCls} size={panelSizes[idx]} />
<InternalPanel
{...panelProps}
prefixCls={prefixCls}
size={panelSizes[idx]}
supportMotion={collapsible?.motion && movingIndex === undefined}
/>
);
// Split Bar

View File

@@ -4,12 +4,40 @@ exports[`renders components/splitter/demo/collapsible.tsx extend context correct
<div
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-flex css-var-test-id ant-flex-gap-middle"
>
<button
aria-checked="true"
class="ant-switch css-var-test-id ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
motion
</span>
<span
class="ant-switch-inner-unchecked"
>
motion
</span>
</span>
</button>
</div>
<div
class="ant-splitter ant-splitter-horizontal css-var-test-id ant-splitter-css-var"
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); height: 200px;"
>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis: auto; flex-grow: 1;"
>
<div
@@ -36,7 +64,7 @@ exports[`renders components/splitter/demo/collapsible.tsx extend context correct
/>
</div>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis: auto; flex-grow: 1;"
>
<div
@@ -57,7 +85,7 @@ exports[`renders components/splitter/demo/collapsible.tsx extend context correct
style="box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); height: 300px;"
>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis: auto; flex-grow: 1;"
>
<div
@@ -84,7 +112,7 @@ exports[`renders components/splitter/demo/collapsible.tsx extend context correct
/>
</div>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis: auto; flex-grow: 1;"
>
<div

View File

@@ -4,12 +4,40 @@ exports[`renders components/splitter/demo/collapsible.tsx correctly 1`] = `
<div
class="ant-flex css-var-test-id ant-flex-align-stretch ant-flex-gap-middle ant-flex-vertical"
>
<div
class="ant-flex css-var-test-id ant-flex-gap-middle"
>
<button
aria-checked="true"
class="ant-switch css-var-test-id ant-switch-checked"
role="switch"
type="button"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
>
<span
class="ant-switch-inner-checked"
>
motion
</span>
<span
class="ant-switch-inner-unchecked"
>
motion
</span>
</span>
</button>
</div>
<div
class="ant-splitter ant-splitter-horizontal css-var-test-id ant-splitter-css-var"
style="box-shadow:0 0 10px rgba(0, 0, 0, 0.1);height:200px"
>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis:auto;flex-grow:1"
>
<div
@@ -36,7 +64,7 @@ exports[`renders components/splitter/demo/collapsible.tsx correctly 1`] = `
/>
</div>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis:auto;flex-grow:1"
>
<div
@@ -57,7 +85,7 @@ exports[`renders components/splitter/demo/collapsible.tsx correctly 1`] = `
style="box-shadow:0 0 10px rgba(0, 0, 0, 0.1);height:300px"
>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis:auto;flex-grow:1"
>
<div
@@ -84,7 +112,7 @@ exports[`renders components/splitter/demo/collapsible.tsx correctly 1`] = `
/>
</div>
<div
class="ant-splitter-panel"
class="ant-splitter-panel ant-splitter-panel-transition"
style="flex-basis:auto;flex-grow:1"
>
<div

View File

@@ -997,6 +997,21 @@ describe('Splitter', () => {
expect(onCollapse).toHaveBeenCalledTimes(2);
expect(onCollapse).toHaveBeenCalledWith([false, false], [50, 50]);
});
it('should apply transition when motion is true', async () => {
const { container } = render(
<SplitterDemo
items={[{ collapsible: true }, { collapsible: true }]}
collapsible={{
motion: true,
}}
/>,
);
expect(container.querySelector('.ant-splitter-panel')).toHaveClass(
'ant-splitter-panel-transition',
);
});
});
it('auto resize', async () => {

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Flex, Splitter, Typography } from 'antd';
import React, { useState } from 'react';
import { Flex, Splitter, Switch, Typography } from 'antd';
import type { SplitterProps } from 'antd';
const Desc: React.FC<Readonly<{ text?: string | number }>> = (props) => (
@@ -21,11 +21,23 @@ const CustomSplitter: React.FC<Readonly<SplitterProps>> = ({ style, ...restProps
</Splitter>
);
const App: React.FC = () => (
<Flex gap="middle" vertical>
<CustomSplitter style={{ height: 200 }} />
<CustomSplitter style={{ height: 300 }} orientation="vertical" />
</Flex>
);
const App: React.FC = () => {
const [motion, setMotion] = useState(true);
return (
<Flex vertical gap="middle">
<Flex gap="middle">
<Switch
checked={motion}
onChange={setMotion}
checkedChildren="motion"
unCheckedChildren="motion"
/>
</Flex>
<CustomSplitter style={{ height: 200 }} collapsible={{ motion }} />
<CustomSplitter style={{ height: 300 }} orientation="vertical" collapsible={{ motion }} />
</Flex>
);
};
export default App;

View File

@@ -44,6 +44,7 @@ Common props ref[Common props](/docs/react/common-props)
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| classNames | Customize class for each semantic structure inside the component. Supports object or function. | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
| collapsible | Collapse config. Set `motion: true` to enable collapse animation; duration is controlled by Component Token `panelMotionDuration` (inherits from `motionDurationSlow`) | `{ motion?: boolean }` | - | 6.4.0 |
| collapsibleIcon | custom collapsible icon | `{start: ReactNode; end: ReactNode}` | - | 6.0.0 |
| draggerIcon | custom dragger icon | `ReactNode` | - | 6.0.0 |
| ~~layout~~ | Layout direction | `horizontal` \| `vertical` | `horizontal` | - |

View File

@@ -45,6 +45,7 @@ demo:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| classNames | 用于自定义组件内部各语义化结构的 class支持对象或函数 | Record<[SemanticDOM](#semantic-dom), string> \| (info: { props })=> Record<[SemanticDOM](#semantic-dom), string> | - | |
| collapsible | 折叠配置。`motion: true` 时启用折叠动画,时长由组件 Token `panelMotionDuration` 控制(继承自 `motionDurationSlow` | `{ motion?: boolean }` | - | 6.4.0 |
| collapsibleIcon | 折叠图标 | `{start?: ReactNode; end?: ReactNode}` | - | 6.0.0 |
| draggerIcon | 拖拽图标 | `ReactNode` | - | 6.0.0 |
| ~~layout~~ | 布局方向 | `horizontal` \| `vertical` | `horizontal` | - |

View File

@@ -46,6 +46,10 @@ export interface SplitterProps {
prefixCls?: string;
className?: string;
classNames?: SplitterClassNamesType;
/**
* Collapse configuration. Set `motion: true` to enable collapse animation (duration follows Component Token).
*/
collapsible?: { motion?: boolean };
style?: React.CSSProperties;
styles?: SplitterStylesType;
rootClassName?: string;
@@ -87,6 +91,7 @@ export interface PanelProps {
export interface InternalPanelProps extends PanelProps {
className?: string;
prefixCls?: string;
supportMotion?: boolean;
}
export interface UseResizeProps extends Pick<SplitterProps, 'onResize'> {

View File

@@ -1,6 +1,7 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { resetComponent } from '../../style';
import { genNoMotionStyle } from '../../style/motion';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks } from '../../theme/internal';
import { genCssVar } from '../../theme/util/genStyleUtils';
@@ -374,6 +375,11 @@ const genSplitterStyle: GenerateStyle<SplitterToken> = (token: SplitterToken): C
[`&:has(${componentCls}:only-child)`]: {
overflow: 'hidden',
},
'&-transition': {
transition: `flex-basis ${token.motionDurationSlow} ${token.motionEaseInOut}`,
...genNoMotionStyle(),
},
},
},
};

View File

@@ -3,7 +3,7 @@ import type { StepsProps } from 'antd';
import { Button, Space, Steps } from 'antd';
const App: React.FC = () => {
const [percent, setPercentage] = useState<number | undefined>(0);
const [percent, setPercent] = useState<number | undefined>(0);
const [current, setCurrent] = useState(1);
const [status, setStatus] = useState<StepsProps['status']>('process');
const content = 'This is a content.';
@@ -25,10 +25,10 @@ const App: React.FC = () => {
return (
<>
<Space.Compact block>
<Button onClick={() => setPercentage(undefined)}>Percentage to undefined</Button>
<Button onClick={() => setPercent(undefined)}>Percentage to undefined</Button>
<Button
onClick={() =>
setPercentage((prev) => {
setPercent((prev) => {
const next = (prev ?? 0) + 10;
return next > 100 ? 0 : next;
})

View File

@@ -22,6 +22,7 @@ import {
slideUpIn,
slideUpOut,
} from './slide';
import { genNoMotionStyle } from './util';
import {
initZoomMotion,
zoomBigIn,
@@ -42,6 +43,7 @@ export {
fadeIn,
fadeOut,
genCollapseMotion,
genNoMotionStyle,
initFadeMotion,
initMoveMotion,
initSlideMotion,

View File

@@ -0,0 +1,10 @@
import type { CSSObject } from '@ant-design/cssinjs';
export const genNoMotionStyle = (): CSSObject => {
return {
'@media (prefers-reduced-motion: reduce)': {
transition: 'none',
animation: 'none',
},
};
};

View File

@@ -162,6 +162,7 @@ type ZoomMotionTypes =
| 'zoom-right'
| 'zoom-up'
| 'zoom-down';
const zoomMotion: Record<ZoomMotionTypes, { inKeyframes: Keyframes; outKeyframes: Keyframes }> = {
zoom: {
inKeyframes: zoomIn,

View File

@@ -3,6 +3,7 @@ import { unit } from '@ant-design/cssinjs';
import { FastColor } from '@ant-design/fast-color';
import { genFocusStyle, resetComponent } from '../../style';
import { genNoMotionStyle } from '../../style/motion';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
@@ -208,6 +209,7 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
width: handleSize,
height: handleSize,
transition: `all ${token.switchDuration} ease-in-out`,
...genNoMotionStyle(),
'&::before': {
position: 'absolute',
@@ -220,6 +222,7 @@ const genSwitchHandleStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
boxShadow: handleShadow,
transition: `all ${token.switchDuration} ease-in-out`,
content: '""',
...genNoMotionStyle(),
},
},
@@ -270,7 +273,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
transition: [`padding-inline-start`, `padding-inline-end`]
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
.join(', '),
...genNoMotionStyle(),
[`${switchInnerCls}-checked, ${switchInnerCls}-unchecked`]: {
display: 'block',
color: token.colorTextLightSolid,
@@ -280,6 +283,7 @@ const genSwitchInnerStyle: GenerateStyle<SwitchToken, CSSObject> = (token) => {
transition: [`margin-inline-start`, `margin-inline-end`]
.map((prop) => `${prop} ${switchDuration} ease-in-out`)
.join(', '),
...genNoMotionStyle(),
},
[`${switchInnerCls}-checked`]: {
@@ -347,7 +351,7 @@ const genSwitchStyle = (token: SwitchToken): CSSObject => {
cursor: 'pointer',
transition: `all ${token.motionDurationMid}`,
userSelect: 'none',
...genNoMotionStyle(),
[`&:hover:not(${componentCls}-disabled)`]: {
background: token.colorTextTertiary,
},

View File

@@ -2010,7 +2010,7 @@ describe('Table.filter', () => {
];
const App: React.FC = () => {
const [ddd, setData] = React.useState<Array<DataType>>([
const [data, setData] = React.useState<Array<DataType>>([
{
key: '1',
name: 'John Brown',
@@ -2089,7 +2089,7 @@ describe('Table.filter', () => {
<span className="rest-btn" onClick={handleClick}>
refresh
</span>
<Table columns={cs} dataSource={ddd} />
<Table columns={cs} dataSource={data} />
</div>
);
};

View File

@@ -49,6 +49,7 @@ import type { ComponentToken as SelectComponentToken } from '../../select/style'
import type { ComponentToken as SkeletonComponentToken } from '../../skeleton/style';
import type { ComponentToken as SliderComponentToken } from '../../slider/style';
import type { ComponentToken as SpaceComponentToken } from '../../space/style';
import type { ComponentToken as AddonComponentToken } from '../../space/style/addon';
import type { ComponentToken as SpinComponentToken } from '../../spin/style';
import type { ComponentToken as SplitterComponentToken } from '../../splitter/style';
import type { ComponentToken as StatisticComponentToken } from '../../statistic/style';
@@ -68,6 +69,7 @@ import type { ComponentToken as UploadComponentToken } from '../../upload/style'
export interface ComponentTokenMap {
Affix?: AffixComponentToken;
Addon?: AddonComponentToken;
Alert?: AlertComponentToken;
Anchor?: AnchorComponentToken;
Avatar?: AvatarComponentToken;

View File

@@ -128,7 +128,7 @@ describe('Tour', () => {
it('button props onClick', () => {
const App: React.FC = () => {
const coverBtnRef = useRef<HTMLButtonElement>(null);
const [btnName, steBtnName] = React.useState<string>('defaultBtn');
const [btnName, setBtnName] = React.useState<string>('defaultBtn');
return (
<>
<span id="btnName">{btnName}</span>
@@ -143,17 +143,17 @@ describe('Tour', () => {
description: '',
target: () => coverBtnRef.current!,
nextButtonProps: {
onClick: () => steBtnName('nextButton'),
onClick: () => setBtnName('nextButton'),
},
},
{
title: '',
target: () => coverBtnRef.current!,
prevButtonProps: {
onClick: () => steBtnName('prevButton'),
onClick: () => setBtnName('prevButton'),
},
nextButtonProps: {
onClick: () => steBtnName('finishButton'),
onClick: () => setBtnName('finishButton'),
},
},
]}

View File

@@ -37,10 +37,10 @@ const treeData = [
},
];
const App: React.FC = () => {
const [placement, SetPlacement] = useState<SelectCommonPlacement>('topLeft');
const [placement, setPlacement] = useState<SelectCommonPlacement>('topLeft');
const placementChange = (e: RadioChangeEvent) => {
SetPlacement(e.target.value);
setPlacement(e.target.value);
};
return (

View File

@@ -21,12 +21,6 @@ export function getNode(dom: React.ReactNode, defaultNode: React.ReactNode, need
export function isEleEllipsis(ele: HTMLElement): boolean {
// Create a new div to get the size
const childDiv = document.createElement('em');
// Set styles to prevent the child element from affecting layout measurements
// line-height: 0 prevents font metrics from causing false positives
childDiv.style.lineHeight = '0';
childDiv.style.verticalAlign = 'top';
ele.appendChild(childDiv);
// For test case

View File

@@ -387,16 +387,10 @@ describe('Typography.Ellipsis', () => {
'ant-typography-css-ellipsis-content-measure',
)
) {
// Check if line-height is set to 0 (from the fix)
const element = this as unknown as HTMLElement;
const lineHeight = element.style.lineHeight;
// 22 is the default LINE_HEIGHT used in the test setup
const height = lineHeight === '0' ? 0 : 22;
return {
...measureRect,
right: measureRect.left,
bottom: measureRect.top + height,
bottom: measureRect.top + 22,
};
}
@@ -508,29 +502,6 @@ describe('Typography.Ellipsis', () => {
expect(baseElement.querySelector('.ant-tooltip-open')).toBeFalsy();
});
// Fix for font metric issue where em element height differs from container
it('should not show tooltip when em height differs due to font metrics', async () => {
// Simulate scenario where:
// - Container and measure have same horizontal bounds (no horizontal overflow)
// - Without fix: em element height would be 20px, container is 18px
// - With fix: em element height is 0px (line-height: 0)
containerRect.right = 100;
containerRect.bottom = 18;
measureRect.left = 0;
measureRect.top = 0;
const { container, baseElement } = await getWrapper({
title: true,
});
fireEvent.mouseEnter(container.firstChild!);
await waitFakeTimer();
// With the fix, tooltip should not show because text is not actually ellipsed
// The em element now has line-height: 0, so its height won't exceed container height
expect(baseElement.querySelector('.ant-tooltip-open')).toBeFalsy();
});
});
});
@@ -673,12 +644,12 @@ describe('Typography.Ellipsis', () => {
it('Switch locale', async () => {
const ref = React.createRef<HTMLElement>();
const App = () => {
const [locale, setLocal] = React.useState<Locale>();
const [locale, setLocale] = React.useState<Locale>();
return (
<ConfigProvider locale={locale}>
<div>
<button type="button" onClick={() => setLocal(zhCN)}>
<button type="button" onClick={() => setLocale(zhCN)}>
zhcn
</button>
<Base

View File

@@ -65,7 +65,6 @@ export default antfu(
'react-hooks/refs': 'off',
'react/no-implicit-key': 'off',
'react-naming-convention/ref-name': 'off',
'react-naming-convention/use-state': 'off',
},
},
{