Compare commits

...

7 Commits

Author SHA1 Message Date
github-actions[bot] b90aac7d28
chore: auto merge branches (#43662) 1 year ago
MuxinFeng 084f234bc9
refactor: Select deprecated showArrow prop (#43520) 1 year ago
红果汁 ecb36840bf
feat: Descriptions implement `items` API (#43483) 1 year ago
github-actions[bot] b0d69a6878
chore: auto merge branches (#43575) 1 year ago
红果汁 c377457104
feat: ColorPicker implement `disabledAlpha` API (#43355) 1 year ago
github-actions[bot] 493b66b641
chore: auto merge branches (#43498) 1 year ago
二货爱吃白萝卜 b072d3a02c
feat: Modal hooks confirm function support await (#43470) 1 year ago
  1. 12
      components/_util/ActionButton.tsx
  2. 4
      components/auto-complete/index.tsx
  3. 13
      components/cascader/__tests__/index.test.tsx
  4. 18
      components/cascader/index.tsx
  5. 38
      components/color-picker/ColorPicker.tsx
  6. 285
      components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap
  7. 15
      components/color-picker/__tests__/__snapshots__/demo.test.ts.snap
  8. 53
      components/color-picker/__tests__/index.test.tsx
  9. 8
      components/color-picker/components/ColorInput.tsx
  10. 12
      components/color-picker/components/PanelPicker.tsx
  11. 7
      components/color-picker/demo/disabled-alpha.md
  12. 6
      components/color-picker/demo/disabled-alpha.tsx
  13. 2
      components/color-picker/index.en-US.md
  14. 2
      components/color-picker/index.zh-CN.md
  15. 1
      components/color-picker/interface.ts
  16. 11
      components/color-picker/style/picker.ts
  17. 6
      components/color-picker/util.ts
  18. 24
      components/descriptions/Row.tsx
  19. 436
      components/descriptions/__tests__/__snapshots__/demo-extend.test.ts.snap
  20. 398
      components/descriptions/__tests__/__snapshots__/demo.test.ts.snap
  21. 86
      components/descriptions/__tests__/__snapshots__/index.test.tsx.snap
  22. 27
      components/descriptions/__tests__/index.test.tsx
  23. 43
      components/descriptions/demo/basic.tsx
  24. 105
      components/descriptions/demo/border.tsx
  25. 129
      components/descriptions/demo/component-token.tsx
  26. 7
      components/descriptions/demo/jsx.md
  27. 16
      components/descriptions/demo/jsx.tsx
  28. 69
      components/descriptions/demo/responsive.tsx
  29. 127
      components/descriptions/demo/size.tsx
  30. 64
      components/descriptions/demo/style.tsx
  31. 121
      components/descriptions/demo/text.tsx
  32. 103
      components/descriptions/demo/vertical-border.tsx
  33. 44
      components/descriptions/demo/vertical.tsx
  34. 80
      components/descriptions/hooks/useRow.ts
  35. 48
      components/descriptions/index.en-US.md
  36. 71
      components/descriptions/index.tsx
  37. 48
      components/descriptions/index.zh-CN.md
  38. 25
      components/modal/ConfirmDialog.tsx
  39. 44
      components/modal/__tests__/hook.test.tsx
  40. 4
      components/modal/demo/hooks.md
  41. 9
      components/modal/demo/hooks.tsx
  42. 15
      components/modal/index.en-US.md
  43. 15
      components/modal/index.zh-CN.md
  44. 8
      components/modal/useModal/HookModal.tsx
  45. 35
      components/modal/useModal/index.tsx
  46. 13
      components/select/__tests__/index.test.tsx
  47. 1
      components/select/demo/custom-tag-render.tsx
  48. 2
      components/select/demo/search-box.tsx
  49. 3
      components/select/index.en-US.md
  50. 22
      components/select/index.tsx
  51. 3
      components/select/index.zh-CN.md
  52. 8
      components/select/useShowArrow.ts
  53. 21
      components/select/utils/iconUtil.tsx
  54. 13
      components/tree-select/__tests__/index.test.tsx
  55. 3
      components/tree-select/index.en-US.md
  56. 20
      components/tree-select/index.tsx
  57. 3
      components/tree-select/index.zh-CN.md
  58. 6
      package.json

12
components/_util/ActionButton.tsx

@ -14,6 +14,11 @@ export interface ActionButtonProps {
emitEvent?: boolean;
quitOnNullishReturnValue?: boolean;
children?: React.ReactNode;
/**
* Do not throw if is await mode
*/
isSilent?: () => boolean;
}
function isThenable<T extends any>(thing?: PromiseLike<T>): boolean {
@ -29,6 +34,7 @@ const ActionButton: React.FC<ActionButtonProps> = (props) => {
close,
autoFocus,
emitEvent,
isSilent,
quitOnNullishReturnValue,
actionFn,
} = props;
@ -70,6 +76,12 @@ const ActionButton: React.FC<ActionButtonProps> = (props) => {
// See: https://github.com/ant-design/ant-design/issues/6183
setLoading(false, true);
clickedRef.current = false;
// Do not throw if is `await` mode
if (isSilent?.()) {
return;
}
return Promise.reject(e);
},
);

4
components/auto-complete/index.tsx

@ -33,7 +33,7 @@ export interface AutoCompleteProps<
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
> extends Omit<
InternalSelectProps<ValueType, OptionType>,
'inputIcon' | 'loading' | 'mode' | 'optionLabelProp' | 'labelInValue'
'loading' | 'mode' | 'optionLabelProp' | 'labelInValue'
> {
dataSource?: DataSourceItemType[];
status?: InputStatus;
@ -142,7 +142,7 @@ const AutoComplete: React.ForwardRefRenderFunction<RefSelectProps, AutoCompleteP
return (
<Select
ref={ref}
showArrow={false}
suffixIcon={null}
{...omit(props, ['dataSource', 'dropdownClassName'])}
prefixCls={prefixCls}
popupClassName={popupClassName || dropdownClassName}

13
components/cascader/__tests__/index.test.tsx

@ -715,4 +715,17 @@ describe('Cascader', () => {
fireEvent.click(container.querySelector('.ant-cascader-checkbox')!);
expect(container.querySelectorAll('.ant-cascader-checkbox-checked')).toHaveLength(3);
});
it('deprecate showArrow', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(<Cascader showArrow />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] `showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
expect(container.querySelector('.ant-select-show-arrow')).toBeTruthy();
errSpy.mockRestore();
});
});

18
components/cascader/index.tsx

@ -119,6 +119,11 @@ export type CascaderProps<DataNodeType extends BaseOptionType = any> =
UnionCascaderProps<DataNodeType> & {
multiple?: boolean;
size?: SizeType;
/**
* @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a
* default behavior, you can hide it by setting `suffixIcon` to null.
*/
showArrow?: boolean;
disabled?: boolean;
bordered?: boolean;
placement?: SelectCommonPlacement;
@ -194,6 +199,12 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
'Cascader',
'`dropdownClassName` is deprecated. Please use `popupClassName` instead.',
);
warning(
!('showArrow' in props),
'Cascader',
'`showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
}
// =================== No Found ====================
@ -267,12 +278,12 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
);
// ===================== Icons =====================
const mergedShowArrow = useShowArrow(showArrow);
const showSuffixIcon = useShowArrow(props.suffixIcon, showArrow);
const { suffixIcon, removeIcon, clearIcon } = getIcons({
...props,
hasFeedback,
feedbackIcon,
showArrow: mergedShowArrow,
showSuffixIcon,
multiple,
prefixCls,
});
@ -317,7 +328,7 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
allowClear={allowClear}
showSearch={mergedShowSearch}
expandIcon={mergedExpandIcon}
inputIcon={suffixIcon}
suffixIcon={suffixIcon}
removeIcon={removeIcon}
clearIcon={clearIcon}
loadingIcon={loadingIcon}
@ -332,7 +343,6 @@ const Cascader = React.forwardRef<CascaderRef, CascaderProps<any>>((props, ref)
)}
getPopupContainer={getPopupContainer || getContextPopupContainer}
ref={ref}
showArrow={hasFeedback || mergedShowArrow}
/>
);

38
components/color-picker/ColorPicker.tsx

@ -5,9 +5,10 @@ import type {
import classNames from 'classnames';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { CSSProperties, FC } from 'react';
import React, { useContext, useRef, useState } from 'react';
import React, { useContext, useMemo, useRef, useState } from 'react';
import genPurePanel from '../_util/PurePanel';
import { getStatusClassNames } from '../_util/statusUtils';
import warning from '../_util/warning';
import type { SizeType } from '../config-provider/SizeContext';
import type { ConfigConsumerProps } from '../config-provider/context';
import { ConfigContext } from '../config-provider/context';
@ -29,11 +30,11 @@ import type {
TriggerType,
} from './interface';
import useStyle from './style/index';
import { customizePrefixCls, generateColor } from './util';
import { customizePrefixCls, genAlphaColor, generateColor, getAlphaColor } from './util';
export type ColorPickerProps = Omit<
RcColorPickerProps,
'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'onChangeComplete'
'onChange' | 'value' | 'defaultValue' | 'panelRender' | 'disabledAlpha' | 'onChangeComplete'
> & {
value?: ColorValueType;
defaultValue?: ColorValueType;
@ -54,6 +55,7 @@ export type ColorPickerProps = Omit<
size?: SizeType;
styles?: { popup?: CSSProperties; popupOverlayInner?: CSSProperties };
rootClassName?: string;
disabledAlpha?: boolean;
onOpenChange?: (open: boolean) => void;
onFormatChange?: (format: ColorFormat) => void;
onChange?: (value: Color, hex: string) => void;
@ -85,6 +87,7 @@ const ColorPicker: CompoundedComponent = (props) => {
size: customizeSize,
rootClassName,
styles,
disabledAlpha = false,
onFormatChange,
onChange,
onClear,
@ -117,6 +120,8 @@ const ColorPicker: CompoundedComponent = (props) => {
const prefixCls = getPrefixCls('color-picker', customizePrefixCls);
const isAlphaColor = useMemo(() => getAlphaColor(colorValue) < 100, [colorValue]);
// ===================== Form Status =====================
const { status: contextStatus } = React.useContext(FormItemInputContext);
@ -140,18 +145,29 @@ const ColorPicker: CompoundedComponent = (props) => {
const popupAllowCloseRef = useRef(true);
// ===================== Warning ======================
if (process.env.NODE_ENV !== 'production') {
warning(
!(disabledAlpha && isAlphaColor),
'ColorPicker',
'`disabledAlpha` will make the alpha to be 100% when use alpha color.',
);
}
const handleChange = (data: Color, type?: HsbaColorType, pickColor?: boolean) => {
let color: Color = generateColor(data);
const isNull = value === null || (!value && defaultValue === null);
if (colorCleared || isNull) {
setColorCleared(false);
const hsba = color.toHsb();
// ignore alpha slider
if (colorValue.toHsb().a === 0 && type !== 'alpha') {
hsba.a = 1;
color = generateColor(hsba);
if (getAlphaColor(colorValue) === 0 && type !== 'alpha') {
color = genAlphaColor(color);
}
}
// ignore alpha color
if (disabledAlpha && isAlphaColor) {
color = genAlphaColor(color);
}
// Only for drag-and-drop color picking
if (pickColor) {
popupAllowCloseRef.current = false;
@ -168,7 +184,12 @@ const ColorPicker: CompoundedComponent = (props) => {
const handleChangeComplete: ColorPickerProps['onChangeComplete'] = (color) => {
popupAllowCloseRef.current = true;
onChangeComplete?.(generateColor(color));
let changeColor = generateColor(color);
// ignore alpha color
if (disabledAlpha && isAlphaColor) {
changeColor = genAlphaColor(color);
}
onChangeComplete?.(changeColor);
};
const popoverProps: PopoverProps = {
@ -188,6 +209,7 @@ const ColorPicker: CompoundedComponent = (props) => {
allowClear,
colorCleared,
disabled,
disabledAlpha,
presets,
panelRender,
format: formatValue,

285
components/color-picker/__tests__/__snapshots__/demo-extend.test.ts.snap

@ -1920,6 +1920,291 @@ Array [
]
`;
exports[`renders components/color-picker/demo/disabled-alpha.tsx extend context correctly 1`] = `
Array [
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>,
<div
class="ant-popover ant-zoom-big-appear ant-zoom-big-appear-prepare ant-zoom-big ant-color-picker ant-popover-placement-bottomLeft"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box;"
>
<div
class="ant-popover-arrow"
style="position: absolute;"
/>
<div
class="ant-popover-content"
>
<div
class="ant-popover-inner"
role="tooltip"
>
<div
class="ant-popover-inner-content"
>
<div
class="ant-color-picker-inner-content"
>
<div
class="ant-color-picker-panel"
>
<div
class="ant-color-picker-select"
>
<div
class="ant-color-picker-palette"
style="position: relative;"
>
<div
style="position: absolute; left: 0px; top: 0px; z-index: 1;"
>
<div
class="ant-color-picker-handler"
style="background-color: rgb(22, 119, 255);"
/>
</div>
<div
class="ant-color-picker-saturation"
style="background-color: rgb(0, 0, 0);"
/>
</div>
</div>
<div
class="ant-color-picker-slider-container"
>
<div
class="ant-color-picker-slider-group ant-color-picker-slider-group-disabled-alpha"
>
<div
class="ant-color-picker-slider ant-color-picker-slider-hue"
>
<div
class="ant-color-picker-palette"
style="position: relative;"
>
<div
style="position: absolute; left: 0px; top: 0px; z-index: 1;"
>
<div
class="ant-color-picker-handler ant-color-picker-handler-sm"
style="background-color: rgb(0, 0, 0);"
/>
</div>
<div
class="ant-color-picker-gradient"
style="position: absolute; inset: 0;"
/>
</div>
</div>
</div>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background: rgb(22, 119, 255);"
/>
</div>
</div>
</div>
<div
class="ant-color-picker-input-container"
>
<div
class="ant-select ant-select-sm ant-select-borderless ant-color-picker-format-select ant-select-single ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
aria-autocomplete="list"
aria-controls="rc_select_TEST_OR_SSR_list"
aria-expanded="false"
aria-haspopup="listbox"
aria-owns="rc_select_TEST_OR_SSR_list"
autocomplete="off"
class="ant-select-selection-search-input"
id="rc_select_TEST_OR_SSR"
readonly=""
role="combobox"
style="opacity: 0;"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-item"
title="HEX"
>
HEX
</span>
</div>
<div
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up ant-select-dropdown-placement-bottomRight"
style="--arrow-x: 0px; --arrow-y: 0px; left: -1000vw; top: -1000vh; box-sizing: border-box; width: 68px;"
>
<div>
<div
id="rc_select_TEST_OR_SSR_list"
role="listbox"
style="height: 0px; width: 0px; overflow: hidden;"
>
<div
aria-label="HEX"
aria-selected="true"
id="rc_select_TEST_OR_SSR_list_0"
role="option"
>
hex
</div>
<div
aria-label="HSB"
aria-selected="false"
id="rc_select_TEST_OR_SSR_list_1"
role="option"
>
hsb
</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="true"
class="ant-select-item ant-select-item-option ant-select-item-option-active ant-select-item-option-selected"
title="HEX"
>
<div
class="ant-select-item-option-content"
>
HEX
</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="HSB"
>
<div
class="ant-select-item-option-content"
>
HSB
</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="RGB"
>
<div
class="ant-select-item-option-content"
>
RGB
</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
class="ant-color-picker-input"
>
<span
class="ant-input-affix-wrapper ant-color-picker-hex-input ant-input-affix-wrapper-sm"
>
<span
class="ant-input-prefix"
>
#
</span>
<input
class="ant-input ant-input-sm"
type="text"
value="1677FF"
/>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
]
`;
exports[`renders components/color-picker/demo/format.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"

15
components/color-picker/__tests__/__snapshots__/demo.test.ts.snap

@ -84,6 +84,21 @@ exports[`renders components/color-picker/demo/disabled.tsx correctly 1`] = `
</div>
`;
exports[`renders components/color-picker/demo/disabled-alpha.tsx correctly 1`] = `
<div
class="ant-color-picker-trigger"
>
<div
class="ant-color-picker-color-block"
>
<div
class="ant-color-picker-color-block-inner"
style="background:rgb(22, 119, 255)"
/>
</div>
</div>
`;
exports[`renders components/color-picker/demo/format.tsx correctly 1`] = `
<div
class="ant-space ant-space-vertical"

53
components/color-picker/__tests__/index.test.tsx

@ -4,9 +4,11 @@ import React, { useMemo, useState } from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { waitFakeTimer } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
import ConfigProvider from '../../config-provider';
import Form from '../../form';
import theme from '../../theme';
import type { ColorPickerProps } from '../ColorPicker';
import ColorPicker from '../ColorPicker';
import type { Color } from '../color';
@ -35,11 +37,14 @@ function doMouseMove(
describe('ColorPicker', () => {
mountTest(ColorPicker);
rtlTest(ColorPicker);
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
beforeEach(() => {
resetWarned();
jest.useFakeTimers();
});
afterEach(() => {
errorSpy.mockReset();
jest.useRealTimers();
});
@ -458,4 +463,52 @@ describe('ColorPicker', () => {
doMouseMove(container, 0, 999);
expect(handleChangeComplete).toHaveBeenCalledTimes(1);
});
it('Should disabledAlpha work', async () => {
const { container } = render(<ColorPicker open disabledAlpha />);
expect(container.querySelector('.ant-color-picker-slider-group-disabled-alpha')).toBeTruthy();
expect(container.querySelector('.ant-color-picker-slider-alpha')).toBeFalsy();
expect(container.querySelector('.ant-color-picker-alpha-input')).toBeFalsy();
});
it('Should disabledAlpha work with value', async () => {
spyElementPrototypes(HTMLElement, {
getBoundingClientRect: () => ({
x: 0,
y: 100,
width: 100,
height: 100,
}),
});
const Demo = () => {
const [value, setValue] = useState<ColorPickerProps['value']>('#1677ff86');
const [changedValue, setChangedValue] = useState<ColorPickerProps['value']>('#1677ff86');
return (
<ColorPicker
open
disabledAlpha
value={value}
onChange={setValue}
onChangeComplete={setChangedValue}
>
<div className="color-value">
{typeof value === 'string' ? value : value?.toHexString()}
</div>
<div className="color-value-changed">
{typeof changedValue === 'string' ? changedValue : changedValue?.toHexString()}
</div>
</ColorPicker>
);
};
const { container } = render(<Demo />);
expect(container.querySelector('.color-value')?.innerHTML).toEqual('#1677ff86');
doMouseMove(container, 0, 999);
expect(container.querySelector('.color-value')?.innerHTML).toEqual('#000000');
expect(container.querySelector('.color-value-changed')?.innerHTML).toEqual('#000000');
});
it('Should warning work when set disabledAlpha true and color is alpha color', () => {
render(<ColorPicker disabledAlpha value="#1677ff" />);
expect(errorSpy).not.toHaveBeenCalled();
});
});

8
components/color-picker/components/ColorInput.tsx

@ -11,7 +11,7 @@ import ColorHsbInput from './ColorHsbInput';
import ColorRgbInput from './ColorRgbInput';
interface ColorInputProps
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'format' | 'onFormatChange'> {
extends Pick<ColorPickerBaseProps, 'prefixCls' | 'format' | 'onFormatChange' | 'disabledAlpha'> {
value?: Color;
onChange?: (value: Color) => void;
}
@ -22,7 +22,7 @@ const selectOptions = [ColorFormat.hex, ColorFormat.hsb, ColorFormat.rgb].map((f
}));
const ColorInput: FC<ColorInputProps> = (props) => {
const { prefixCls, format, value, onFormatChange, onChange } = props;
const { prefixCls, format, value, disabledAlpha, onFormatChange, onChange } = props;
const [colorFormat, setColorFormat] = useMergedState(ColorFormat.hex, {
value: format,
onChange: onFormatChange,
@ -61,7 +61,9 @@ const ColorInput: FC<ColorInputProps> = (props) => {
options={selectOptions}
/>
<div className={colorInputPrefixCls}>{steppersNode}</div>
<ColorAlphaInput prefixCls={prefixCls} value={value} onChange={onChange} />
{!disabledAlpha && (
<ColorAlphaInput prefixCls={prefixCls} value={value} onChange={onChange} />
)}
</div>
);
};

12
components/color-picker/components/PanelPicker.tsx

@ -11,7 +11,7 @@ import ColorInput from './ColorInput';
export interface PanelPickerProps
extends Pick<
ColorPickerBaseProps,
'prefixCls' | 'colorCleared' | 'allowClear' | 'onChangeComplete'
'prefixCls' | 'colorCleared' | 'allowClear' | 'disabledAlpha' | 'onChangeComplete'
> {
value?: Color;
onChange?: (value?: Color, type?: HsbaColorType, pickColor?: boolean) => void;
@ -24,6 +24,7 @@ const PanelPicker: FC = () => {
colorCleared,
allowClear,
value,
disabledAlpha,
onChange,
onClear,
onChangeComplete,
@ -46,10 +47,17 @@ const PanelPicker: FC = () => {
<RcColorPicker
prefixCls={prefixCls}
value={value?.toHsb()}
disabledAlpha={disabledAlpha}
onChange={(colorValue, type) => onChange?.(colorValue, type, true)}
onChangeComplete={onChangeComplete}
/>
<ColorInput value={value} onChange={onChange} prefixCls={prefixCls} {...injectProps} />
<ColorInput
value={value}
onChange={onChange}
prefixCls={prefixCls}
disabledAlpha={disabledAlpha}
{...injectProps}
/>
</>
);
};

7
components/color-picker/demo/disabled-alpha.md

@ -0,0 +1,7 @@
## zh-CN
禁用颜色透明度。
## en-US
Disabled color alpha.

6
components/color-picker/demo/disabled-alpha.tsx

@ -0,0 +1,6 @@
import { ColorPicker } from 'antd';
import React from 'react';
const Demo = () => <ColorPicker disabledAlpha />;
export default Demo;

2
components/color-picker/index.en-US.md

@ -25,6 +25,7 @@ Used when the user needs to customize the color selection.
<code src="./demo/change-completed.tsx">Color change completed</code>
<code src="./demo/text-render.tsx">Rendering Trigger Text</code>
<code src="./demo/disabled.tsx">Disable</code>
<code src="./demo/disabled-alpha.tsx">Disabled Alpha</code>
<code src="./demo/allowClear.tsx">Clear Color</code>
<code src="./demo/trigger.tsx">Custom Trigger</code>
<code src="./demo/trigger-event.tsx">Custom Trigger Event</code>
@ -45,6 +46,7 @@ Used when the user needs to customize the color selection.
| children | Trigger of ColorPicker | React.ReactNode | - | |
| defaultValue | Default value of color | string \| `Color` | - | |
| disabled | Disable ColorPicker | boolean | - | |
| disabledAlpha | Disable Alpha | boolean | - | 5.8.0 |
| destroyTooltipOnHide | Whether destroy popover when hidden | `boolean` | false | 5.7.0 |
| format | Format of color | `rgb` \| `hex` \| `hsb` | `hex` | |
| open | Whether to show popup | boolean | - | |

2
components/color-picker/index.zh-CN.md

@ -26,6 +26,7 @@ group:
<code src="./demo/change-completed.tsx">颜色完成选择</code>
<code src="./demo/text-render.tsx">渲染触发器文本</code>
<code src="./demo/disabled.tsx">禁用</code>
<code src="./demo/disabled-alpha.tsx">禁用透明度</code>
<code src="./demo/allowClear.tsx">清除颜色</code>
<code src="./demo/trigger.tsx">自定义触发器</code>
<code src="./demo/trigger-event.tsx">自定义触发事件</code>
@ -46,6 +47,7 @@ group:
| children | 颜色选择器的触发器 | React.ReactNode | - | |
| defaultValue | 颜色默认的值 | string \| `Color` | - | |
| disabled | 禁用颜色选择器 | boolean | - | |
| disabledAlpha | 禁用透明度 | boolean | - | 5.8.0 |
| destroyTooltipOnHide | 关闭后是否销毁弹窗 | `boolean` | false | 5.7.0 |
| format | 颜色格式 | `rgb` \| `hex` \| `hsb` | `hex` | |
| open | 是否显示弹出窗口 | boolean | - | |

1
components/color-picker/interface.ts

@ -28,6 +28,7 @@ export interface ColorPickerBaseProps {
allowClear?: boolean;
colorCleared?: boolean;
disabled?: boolean;
disabledAlpha?: boolean;
presets?: PresetsItem[];
panelRender?: ColorPickerProps['panelRender'];
onFormatChange?: ColorPickerProps['onFormatChange'];

11
components/color-picker/style/picker.ts

@ -58,14 +58,23 @@ const genPickerStyle: GenerateStyle<ColorPickerToken, CSSObject> = (token) => {
boxShadow: colorPickerInsetShadow,
},
'&-alpha': getTransBg(`${colorPickerSliderHeight}px`, token.colorFillSecondary),
marginBottom: marginSM,
'&-hue': { marginBottom: marginSM },
},
[`${componentCls}-slider-container`]: {
display: 'flex',
gap: marginSM,
marginBottom: marginSM,
[`${componentCls}-slider-group`]: {
flex: 1,
'&-disabled-alpha': {
display: 'flex',
alignItems: 'center',
[`${componentCls}-slider`]: {
flex: 1,
marginBottom: 0,
},
},
},
},
};

6
components/color-picker/util.ts

@ -14,3 +14,9 @@ export const generateColor = (color: ColorGenInput<Color>): Color => {
export const getRoundNumber = (value: number) => Math.round(Number(value || 0));
export const getAlphaColor = (color: Color) => getRoundNumber(color.toHsb().a * 100);
export const genAlphaColor = (color: Color, alpha?: number) => {
const hsba = color.toHsb();
hsba.a = alpha || 1;
return generateColor(hsba);
};

24
components/descriptions/Row.tsx

@ -1,8 +1,8 @@
import * as React from 'react';
import type { DescriptionsItemType } from '.';
import Cell from './Cell';
import type { DescriptionsContextProps } from './DescriptionsContext';
import DescriptionsContext from './DescriptionsContext';
import type { DescriptionsItemProps } from './Item';
interface CellConfig {
component: string | [string, string];
@ -12,7 +12,7 @@ interface CellConfig {
}
function renderCells(
items: React.ReactElement<DescriptionsItemProps>[],
items: DescriptionsItemType[],
{ colon, prefixCls, bordered }: RowProps,
{
component,
@ -26,16 +26,14 @@ function renderCells(
return items.map(
(
{
props: {
label,
children,
prefixCls: itemPrefixCls = prefixCls,
className,
style,
labelStyle,
contentStyle,
span = 1,
},
label,
children,
prefixCls: itemPrefixCls = prefixCls,
className,
style,
labelStyle,
contentStyle,
span = 1,
key,
},
index,
@ -89,7 +87,7 @@ function renderCells(
export interface RowProps {
prefixCls: string;
vertical: boolean;
row: React.ReactElement<DescriptionsItemProps>[];
row: DescriptionsItemType[];
bordered?: boolean;
colon: boolean;
index: number;

436
components/descriptions/__tests__/__snapshots__/demo-extend.test.ts.snap

@ -523,7 +523,7 @@ exports[`renders components/descriptions/demo/component-token.tsx extend context
colspan="1"
>
<span>
time
Time
</span>
</th>
<td
@ -720,7 +720,7 @@ exports[`renders components/descriptions/demo/component-token.tsx extend context
<span
class="ant-descriptions-item-label"
>
time
Time
</span>
<span
class="ant-descriptions-item-content"
@ -806,178 +806,316 @@ exports[`renders components/descriptions/demo/component-token.tsx extend context
</div>
`;
exports[`renders components/descriptions/demo/responsive.tsx extend context correctly 1`] = `
<div>
exports[`renders components/descriptions/demo/jsx.tsx extend context correctly 1`] = `
<div
class="ant-descriptions"
>
<div
class="ant-descriptions ant-descriptions-bordered"
class="ant-descriptions-header"
>
<div
class="ant-descriptions-header"
class="ant-descriptions-title"
>
<div
class="ant-descriptions-title"
>
Responsive Descriptions
</div>
User Info
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Product
<span
class="ant-descriptions-item-label"
>
UserName
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Cloud Database
<span
class="ant-descriptions-item-content"
>
Zhou Maomao
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Billing
<span
class="ant-descriptions-item-label"
>
Telephone
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Prepaid
<span
class="ant-descriptions-item-content"
>
1810000000
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
time
<span
class="ant-descriptions-item-label"
>
Live
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
18:00:00
<span
class="ant-descriptions-item-content"
>
Hangzhou, Zhejiang
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Amount
<span
class="ant-descriptions-item-label"
>
Remark
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$80.00
<span
class="ant-descriptions-item-content"
>
empty
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Discount
<span
class="ant-descriptions-item-label"
>
Address
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$20.00
<span
class="ant-descriptions-item-content"
>
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
exports[`renders components/descriptions/demo/responsive.tsx extend context correctly 1`] = `
<div
class="ant-descriptions ant-descriptions-bordered"
>
<div
class="ant-descriptions-header"
>
<div
class="ant-descriptions-title"
>
Responsive Descriptions
</div>
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Official
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$60.00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
<span>
Product
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Config Info
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
</span>
</td>
</tr>
</tbody>
</table>
</div>
<span>
Cloud Database
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Billing
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Prepaid
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Time
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
18:00:00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Amount
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$80.00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Discount
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$20.00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Official
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$60.00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Config Info
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
@ -1125,7 +1263,7 @@ exports[`renders components/descriptions/demo/size.tsx extend context correctly
colspan="1"
>
<span>
time
Time
</span>
</th>
<td
@ -1322,7 +1460,7 @@ exports[`renders components/descriptions/demo/size.tsx extend context correctly
<span
class="ant-descriptions-item-label"
>
time
Time
</span>
<span
class="ant-descriptions-item-content"

398
components/descriptions/__tests__/__snapshots__/demo.test.ts.snap

@ -483,7 +483,7 @@ exports[`renders components/descriptions/demo/component-token.tsx correctly 1`]
colspan="1"
>
<span>
time
Time
</span>
</th>
<td
@ -664,7 +664,7 @@ exports[`renders components/descriptions/demo/component-token.tsx correctly 1`]
<span
class="ant-descriptions-item-label"
>
time
Time
</span>
<span
class="ant-descriptions-item-content"
@ -742,163 +742,289 @@ exports[`renders components/descriptions/demo/component-token.tsx correctly 1`]
</div>
`;
exports[`renders components/descriptions/demo/responsive.tsx correctly 1`] = `
<div>
exports[`renders components/descriptions/demo/jsx.tsx correctly 1`] = `
<div
class="ant-descriptions"
>
<div
class="ant-descriptions ant-descriptions-bordered"
class="ant-descriptions-header"
>
<div
class="ant-descriptions-header"
class="ant-descriptions-title"
>
<div
class="ant-descriptions-title"
>
Responsive Descriptions
</div>
User Info
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Product
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Cloud Database
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Billing
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Prepaid
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
time
<span
class="ant-descriptions-item-label"
>
UserName
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
18:00:00
<span
class="ant-descriptions-item-content"
>
Zhou Maomao
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Amount
<span
class="ant-descriptions-item-label"
>
Telephone
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$80.00
<span
class="ant-descriptions-item-content"
>
1810000000
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
</div>
</td>
<td
class="ant-descriptions-item"
colspan="1"
>
<div
class="ant-descriptions-item-container"
>
<span>
Discount
<span
class="ant-descriptions-item-label"
>
Live
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$20.00
<span
class="ant-descriptions-item-content"
>
Hangzhou, Zhejiang
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<div
class="ant-descriptions-item-container"
>
<span>
Official
<span
class="ant-descriptions-item-label"
>
Remark
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$60.00
<span
class="ant-descriptions-item-content"
>
empty
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
</div>
</td>
<td
class="ant-descriptions-item"
colspan="2"
>
<th
class="ant-descriptions-item-label"
colspan="1"
<div
class="ant-descriptions-item-container"
>
<span>
Config Info
<span
class="ant-descriptions-item-label"
>
Address
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="5"
>
<span>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<span
class="ant-descriptions-item-content"
>
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</span>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
exports[`renders components/descriptions/demo/responsive.tsx correctly 1`] = `
<div
class="ant-descriptions ant-descriptions-bordered"
>
<div
class="ant-descriptions-header"
>
<div
class="ant-descriptions-title"
>
Responsive Descriptions
</div>
</div>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Product
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Cloud Database
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Billing
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
Prepaid
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Time
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
18:00:00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Amount
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$80.00
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Discount
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$20.00
</span>
</td>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Official
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="1"
>
<span>
$60.00
</span>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<th
class="ant-descriptions-item-label"
colspan="1"
>
<span>
Config Info
</span>
</th>
<td
class="ant-descriptions-item-content"
colspan="5"
>
<span>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`;
@ -1037,7 +1163,7 @@ exports[`renders components/descriptions/demo/size.tsx correctly 1`] = `
colspan="1"
>
<span>
time
Time
</span>
</th>
<td
@ -1218,7 +1344,7 @@ exports[`renders components/descriptions/demo/size.tsx correctly 1`] = `
<span
class="ant-descriptions-item-label"
>
time
Time
</span>
<span
class="ant-descriptions-item-content"

86
components/descriptions/__tests__/__snapshots__/index.test.tsx.snap

@ -249,6 +249,92 @@ exports[`Descriptions number 0 should render correct 1`] = `
</div>
`;
exports[`Descriptions should items work 1`] = `
<div>
<div
class="ant-descriptions"
>
<div
class="ant-descriptions-view"
>
<table>
<tbody>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<div
class="ant-descriptions-item-container"
>
<span
class="ant-descriptions-item-label"
>
UserName
</span>
<span
class="ant-descriptions-item-content"
>
Zhou Maomao
</span>
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<div
class="ant-descriptions-item-container"
>
<span
class="ant-descriptions-item-label"
>
Telephone
</span>
<span
class="ant-descriptions-item-content"
>
1810000000
</span>
</div>
</td>
</tr>
<tr
class="ant-descriptions-row"
>
<td
class="ant-descriptions-item"
colspan="1"
>
<div
class="ant-descriptions-item-container"
>
<span
class="ant-descriptions-item-label"
>
Live
</span>
<span
class="ant-descriptions-item-content"
>
Hangzhou, Zhejiang
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
`;
exports[`Descriptions should work with React Fragment 1`] = `
<div
class="ant-descriptions"

27
components/descriptions/__tests__/index.test.tsx

@ -279,4 +279,31 @@ describe('Descriptions', () => {
);
expect(container.querySelectorAll('.ant-descriptions-small')).toHaveLength(1);
});
it('should items work', () => {
const { container } = render(
<Descriptions
items={[
{
key: '1',
label: 'UserName',
children: 'Zhou Maomao',
},
{
key: '2',
label: 'Telephone',
children: '1810000000',
},
{
key: '3',
label: 'Live',
children: 'Hangzhou, Zhejiang',
},
]}
/>,
);
expect(container.querySelector('.ant-descriptions-item')).toBeTruthy();
expect(container.querySelectorAll('.ant-descriptions-item')).toHaveLength(3);
expect(container).toMatchSnapshot();
});
});

43
components/descriptions/demo/basic.tsx

@ -1,16 +1,35 @@
import React from 'react';
import { Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'UserName',
children: 'Zhou Maomao',
},
{
key: '2',
label: 'Telephone',
children: '1810000000',
},
{
key: '3',
label: 'Live',
children: 'Hangzhou, Zhejiang',
},
{
key: '4',
label: 'Remark',
children: 'empty',
},
{
key: '5',
label: 'Address',
children: 'No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China',
},
];
const App: React.FC = () => (
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>
);
const App: React.FC = () => <Descriptions title="User Info" items={items} />;
export default App;

105
components/descriptions/demo/border.tsx

@ -1,36 +1,77 @@
import React from 'react';
import { Badge, Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing Mode',
children: 'Prepaid',
},
{
key: '3',
label: 'Automatic Renewal',
children: 'YES',
},
{
key: '4',
label: 'Order time',
children: '2018-04-24 18:00:00',
},
{
key: '5',
label: 'Usage Time',
children: '2019-04-24 18:00:00',
span: 2,
},
{
key: '6',
label: 'Status',
children: <Badge status="processing" text="Running" />,
span: 3,
},
{
key: '7',
label: 'Negotiated Amount',
children: '$80.00',
},
{
key: '8',
label: 'Discount',
children: '$20.00',
},
{
key: '9',
label: 'Official Receipts',
children: '$60.00',
},
{
key: '10',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</>
),
},
];
const App: React.FC = () => (
<Descriptions title="User Info" bordered>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" span={2}>
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Status" span={3}>
<Badge status="processing" text="Running" />
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</Descriptions.Item>
</Descriptions>
);
const App: React.FC = () => <Descriptions title="User Info" bordered items={items} />;
export default App;

129
components/descriptions/demo/component-token.tsx

@ -1,7 +1,93 @@
import type { RadioChangeEvent } from 'antd';
import { Button, ConfigProvider, Descriptions, Radio } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React, { useState } from 'react';
const borderedItems: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing',
children: 'Prepaid',
},
{
key: '3',
label: 'Time',
children: '18:00:00',
},
{
key: '4',
label: 'Amount',
children: '$80.00',
},
{
key: '5',
label: 'Discount',
children: '$20.00',
},
{
key: '6',
label: 'Official',
children: '$60.00',
},
{
key: '7',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</>
),
},
];
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing',
children: 'Prepaid',
},
{
key: '3',
label: 'Time',
children: '18:00:00',
},
{
key: '4',
label: 'Amount',
children: '$80.00',
},
{
key: '5',
label: 'Discount',
children: '$20.00',
},
{
key: '6',
label: 'Official',
children: '$60.00',
},
];
const App: React.FC = () => {
const [size, setSize] = useState<'default' | 'middle' | 'small'>('default');
@ -33,38 +119,21 @@ const App: React.FC = () => {
</Radio.Group>
<br />
<br />
<Descriptions bordered title="Custom Size" size={size} extra={<div>extra color: blue</div>}>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</Descriptions.Item>
</Descriptions>
<Descriptions
bordered
title="Custom Size"
size={size}
extra={<div>extra color: blue</div>}
items={borderedItems}
/>
<br />
<br />
<Descriptions title="Custom Size" size={size} extra={<Button type="primary">Edit</Button>}>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official">$60.00</Descriptions.Item>
</Descriptions>
<Descriptions
title="Custom Size"
size={size}
extra={<Button type="primary">Edit</Button>}
items={items}
/>
</div>
</ConfigProvider>
);

7
components/descriptions/demo/jsx.md

@ -0,0 +1,7 @@
## zh-CN
JSX 风格演示。
## en-US
JSX Style Demo.

16
components/descriptions/demo/jsx.tsx

@ -0,0 +1,16 @@
import { Descriptions } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>
);
export default App;

69
components/descriptions/demo/responsive.tsx

@ -1,20 +1,43 @@
import React from 'react';
import { Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const App: React.FC = () => (
<div>
<Descriptions
title="Responsive Descriptions"
bordered
column={{ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }}
>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing',
children: 'Prepaid',
},
{
key: '3',
label: 'Time',
children: '18:00:00',
},
{
key: '4',
label: 'Amount',
children: '$80.00',
},
{
key: '5',
label: 'Discount',
children: '$20.00',
},
{
key: '6',
label: 'Official',
children: '$60.00',
},
{
key: '7',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
@ -26,9 +49,19 @@ const App: React.FC = () => (
Replication factor: 3
<br />
Region: East China 1
</Descriptions.Item>
</Descriptions>
</div>
<br />
</>
),
},
];
const App: React.FC = () => (
<Descriptions
title="Responsive Descriptions"
bordered
column={{ xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 }}
items={items}
/>
);
export default App;

127
components/descriptions/demo/size.tsx

@ -1,6 +1,93 @@
import React, { useState } from 'react';
import type { RadioChangeEvent } from 'antd';
import { Button, Descriptions, Radio } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React, { useState } from 'react';
const borderedItems: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing',
children: 'Prepaid',
},
{
key: '3',
label: 'Time',
children: '18:00:00',
},
{
key: '4',
label: 'Amount',
children: '$80.00',
},
{
key: '5',
label: 'Discount',
children: '$20.00',
},
{
key: '6',
label: 'Official',
children: '$60.00',
},
{
key: '7',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</>
),
},
];
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing',
children: 'Prepaid',
},
{
key: '3',
label: 'Time',
children: '18:00:00',
},
{
key: '4',
label: 'Amount',
children: '$80.00',
},
{
key: '5',
label: 'Discount',
children: '$20.00',
},
{
key: '6',
label: 'Official',
children: '$60.00',
},
];
const App: React.FC = () => {
const [size, setSize] = useState<'default' | 'middle' | 'small'>('default');
@ -24,38 +111,16 @@ const App: React.FC = () => {
title="Custom Size"
size={size}
extra={<Button type="primary">Edit</Button>}
>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</Descriptions.Item>
</Descriptions>
items={borderedItems}
/>
<br />
<br />
<Descriptions title="Custom Size" size={size} extra={<Button type="primary">Edit</Button>}>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official">$60.00</Descriptions.Item>
</Descriptions>
<Descriptions
title="Custom Size"
size={size}
extra={<Button type="primary">Edit</Button>}
items={items}
/>
</div>
);
};

64
components/descriptions/demo/style.tsx

@ -1,11 +1,52 @@
import React, { useState } from 'react';
import { Descriptions, Divider, Radio, Switch } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React, { useState } from 'react';
const labelStyle: React.CSSProperties = { background: 'red' };
const contentStyle: React.CSSProperties = { background: 'green' };
type LayoutType = 'horizontal' | 'vertical' | undefined;
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
labelStyle,
contentStyle,
},
{
key: '2',
label: 'Billing Mode',
children: 'Prepaid',
},
{
key: '3',
label: 'Automatic Renewal',
children: 'YES',
},
];
const rootStyleItems: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing Mode',
children: 'Prepaid',
},
{
key: '3',
label: 'Automatic Renewal',
children: 'YES',
labelStyle: { color: 'orange' },
contentStyle: { color: 'blue' },
},
];
const App: React.FC = () => {
const [border, setBorder] = useState(true);
const [layout, setLayout] = useState('horizontal' as LayoutType);
@ -24,13 +65,7 @@ const App: React.FC = () => {
<Radio value="vertical">vertical</Radio>
</Radio.Group>
<Divider />
<Descriptions title="User Info" bordered={border} layout={layout}>
<Descriptions.Item label="Product" labelStyle={labelStyle} contentStyle={contentStyle}>
Cloud Database
</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
</Descriptions>
<Descriptions title="User Info" bordered={border} layout={layout} items={items} />
<Divider />
<Descriptions
title="Root style"
@ -38,17 +73,8 @@ const App: React.FC = () => {
contentStyle={contentStyle}
bordered={border}
layout={layout}
>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item
label="Automatic Renewal"
labelStyle={{ color: 'orange' }}
contentStyle={{ color: 'blue' }}
>
YES
</Descriptions.Item>
</Descriptions>
items={rootStyleItems}
/>
</>
);
};

121
components/descriptions/demo/text.tsx

@ -1,5 +1,6 @@
import React from 'react';
import { Badge, Descriptions, Table } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const dataSource = [
{
@ -34,42 +35,86 @@ const columns = [
},
];
const App: React.FC = () => (
<Descriptions title="User Info" column={2}>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label={<div style={{ display: 'flex' }}>Billing Mode</div>}>
Prepaid
</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" span={2}>
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Status" span={3}>
<Badge status="processing" text="Running" />
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
<Table size="small" pagination={false} dataSource={dataSource} columns={columns} />
</Descriptions.Item>
</Descriptions>
);
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: <div style={{ display: 'flex' }}>Billing Mode</div>,
children: 'Prepaid',
},
{
key: '3',
label: 'Automatic Renewal',
children: 'YES',
},
{
key: '4',
label: 'Order time',
children: '2018-04-24 18:00:00',
},
{
key: '5',
label: 'Usage Time',
span: 2,
children: '2019-04-24 18:00:00',
},
{
key: '6',
label: 'Status',
span: 3,
children: <Badge status="processing" text="Running" />,
},
{
key: '7',
label: 'Negotiated Amount',
children: '$80.00',
},
{
key: '8',
label: 'Discount',
children: '$20.00',
},
{
key: '9',
label: 'Official Receipts',
children: '$60.00',
},
{
key: '10',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</>
),
},
{
key: '11',
label: 'Official Receipts',
children: '$60.00',
},
{
key: '12',
label: 'Config Info',
children: <Table size="small" pagination={false} dataSource={dataSource} columns={columns} />,
},
];
const App: React.FC = () => <Descriptions title="User Info" column={2} items={items} />;
export default App;

103
components/descriptions/demo/vertical-border.tsx

@ -1,36 +1,79 @@
import React from 'react';
import { Badge, Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Product',
children: 'Cloud Database',
},
{
key: '2',
label: 'Billing Mode',
children: 'Prepaid',
},
{
key: '3',
label: 'Automatic Renewal',
children: 'YES',
},
{
key: '4',
label: 'Order time',
children: '2018-04-24 18:00:00',
},
{
key: '5',
label: 'Usage Time',
span: 2,
children: '2019-04-24 18:00:00',
},
{
key: '6',
label: 'Status',
span: 3,
children: <Badge status="processing" text="Running" />,
},
{
key: '7',
label: 'Negotiated Amount',
children: '$80.00',
},
{
key: '8',
label: 'Discount',
children: '$20.00',
},
{
key: '9',
label: 'Official Receipts',
children: '$60.00',
},
{
key: '10',
label: 'Config Info',
children: (
<>
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</>
),
},
];
const App: React.FC = () => (
<Descriptions title="User Info" layout="vertical" bordered>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" span={2}>
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Status" span={3}>
<Badge status="processing" text="Running" />
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1
<br />
</Descriptions.Item>
</Descriptions>
<Descriptions title="User Info" layout="vertical" bordered items={items} />
);
export default App;

44
components/descriptions/demo/vertical.tsx

@ -1,16 +1,36 @@
import React from 'react';
import { Descriptions } from 'antd';
import type { DescriptionsProps } from 'antd/es/descriptions';
import React from 'react';
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'UserName',
children: 'Zhou Maomao',
},
{
key: '2',
label: 'Telephone',
children: '1810000000',
},
{
key: '3',
label: 'Live',
children: 'Hangzhou, Zhejiang',
},
{
key: '4',
label: 'Address',
span: 2,
children: 'No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China',
},
{
key: '5',
label: 'Remark',
children: 'empty',
},
];
const App: React.FC = () => (
<Descriptions title="User Info" layout="vertical">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Address" span={2}>
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
</Descriptions>
);
const App: React.FC = () => <Descriptions title="User Info" layout="vertical" items={items} />;
export default App;

80
components/descriptions/hooks/useRow.ts

@ -0,0 +1,80 @@
import toArray from 'rc-util/lib/Children/toArray';
import type React from 'react';
import { useMemo } from 'react';
import type { DescriptionsItemType } from '..';
import warning from '../../_util/warning';
function getFilledItem(
rowItem: DescriptionsItemType,
rowRestCol: number,
span?: number,
): DescriptionsItemType {
let clone = rowItem;
if (span === undefined || span > rowRestCol) {
clone = {
...rowItem,
span: rowRestCol,
};
warning(
span === undefined,
'Descriptions',
'Sum of column `span` in a line not match `column` of Descriptions.',
);
}
return clone;
}
// Convert children into items
const transChildren2Items = (childNodes?: React.ReactNode) =>
toArray(childNodes).map((node) => node?.props);
// Calculate the sum of span in a row
function getCalcRows(rowItems: DescriptionsItemType[], mergedColumn: number) {
const rows: DescriptionsItemType[][] = [];
let tmpRow: DescriptionsItemType[] = [];
let rowRestCol = mergedColumn;
rowItems
.filter((n) => n)
.forEach((rowItem, index) => {
const span = rowItem?.span;
const mergedSpan = span || 1;
// Additional handle last one
if (index === rowItems.length - 1) {
tmpRow.push(getFilledItem(rowItem, rowRestCol, span));
rows.push(tmpRow);
return;
}
if (mergedSpan < rowRestCol) {
rowRestCol -= mergedSpan;
tmpRow.push(rowItem);
} else {
tmpRow.push(getFilledItem(rowItem, rowRestCol, mergedSpan));
rows.push(tmpRow);
rowRestCol = mergedColumn;
tmpRow = [];
}
});
return rows;
}
const useRow = (
mergedColumn: number,
items?: DescriptionsItemType[],
children?: React.ReactNode,
) => {
const rows = useMemo(() => {
if (Array.isArray(items)) {
return getCalcRows(items, mergedColumn);
}
return getCalcRows(transChildren2Items(children), mergedColumn);
}, [items, children, mergedColumn]);
return rows;
};
export default useRow;

48
components/descriptions/index.en-US.md

@ -12,6 +12,52 @@ Display multiple read-only fields in groups.
Commonly displayed on the details page.
```tsx | pure
// works when >= 5.8.0, recommended ✅
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'UserName',
children: <p>Zhou Maomao</p>,
},
{
key: '2',
label: 'Telephone',
children: <p>1810000000</p>,
},
{
key: '3',
label: 'Live',
children: <p>Hangzhou, Zhejiang</p>,
},
{
key: '4',
label: 'Remark',
children: <p>empty</p>,
},
{
key: '5',
label: 'Address',
children: <p>No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China</p>,
},
];
<Descriptions title="User Info" items={items} />;
// works when <5.8.0 , deprecated when >=5.8.0 🙅🏻‍♀️
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>;
```
## Examples
<!-- prettier-ignore -->
@ -23,6 +69,7 @@ Commonly displayed on the details page.
<code src="./demo/vertical.tsx">Vertical</code>
<code src="./demo/vertical-border.tsx">Vertical border</code>
<code src="./demo/style.tsx" debug>Customize label & wrapper style</code>
<code src="./demo/jsx.tsx" debug>JSX demo</code>
<code src="./demo/component-token.tsx" debug>Component Token</code>
## API
@ -36,6 +83,7 @@ Commonly displayed on the details page.
| column | The number of `DescriptionItems` in a row,could be a number or a object like `{ xs: 8, sm: 16, md: 24}`,(Only set `bordered={true}` to take effect) | number \| [Record<Breakpoint, number>](https://github.com/ant-design/ant-design/blob/84ca0d23ae52e4f0940f20b0e22eabe743f90dca/components/descriptions/index.tsx#L111C21-L111C56) | 3 | |
| contentStyle | Customize content style | CSSProperties | - | 4.10.0 |
| extra | The action area of the description list, placed at the top-right | ReactNode | - | 4.5.0 |
| items | Describe the contents of the list item | [DescriptionsItem](#descriptionitem)[] | - | 5.8.0 |
| labelStyle | Customize label style | CSSProperties | - | 4.10.0 |
| layout | Define description layout | `horizontal` \| `vertical` | `horizontal` | |
| size | Set the size of the list. Can be set to `middle`,`small`, or not filled | `default` \| `middle` \| `small` | - | |

71
components/descriptions/index.tsx

@ -2,17 +2,16 @@
/* eslint-disable react/no-array-index-key */
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import * as React from 'react';
import { cloneElement } from '../_util/reactNode';
import type { Breakpoint, ScreenMap } from '../_util/responsiveObserver';
import useResponsiveObserver, { responsiveArray } from '../_util/responsiveObserver';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useSize from '../config-provider/hooks/useSize';
import DescriptionsContext from './DescriptionsContext';
import type { DescriptionsItemProps } from './Item';
import DescriptionsItem from './Item';
import Row from './Row';
import useRow from './hooks/useRow';
import useStyle from './style';
const DEFAULT_COLUMN_MAP: Record<Breakpoint, number> = {
@ -41,63 +40,14 @@ function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): num
return 3;
}
function getFilledItem(
node: React.ReactElement,
rowRestCol: number,
span?: number,
): React.ReactElement {
let clone = node;
if (span === undefined || span > rowRestCol) {
clone = cloneElement(node, {
span: rowRestCol,
});
warning(
span === undefined,
'Descriptions',
'Sum of column `span` in a line not match `column` of Descriptions.',
);
}
return clone;
}
function getRows(children: React.ReactNode, column: number) {
const childNodes = toArray(children).filter((n) => n);
const rows: React.ReactElement[][] = [];
let tmpRow: React.ReactElement[] = [];
let rowRestCol = column;
childNodes.forEach((node, index) => {
const span: number = node.props?.span;
const mergedSpan = span || 1;
// Additional handle last one
if (index === childNodes.length - 1) {
tmpRow.push(getFilledItem(node, rowRestCol, span));
rows.push(tmpRow);
return;
}
if (mergedSpan < rowRestCol) {
rowRestCol -= mergedSpan;
tmpRow.push(node);
} else {
tmpRow.push(getFilledItem(node, rowRestCol, mergedSpan));
rows.push(tmpRow);
rowRestCol = column;
tmpRow = [];
}
});
return rows;
}
interface CompoundedComponent {
Item: typeof DescriptionsItem;
}
export interface DescriptionsItemType extends DescriptionsItemProps {
key?: React.Key;
}
export interface DescriptionsProps {
prefixCls?: string;
className?: string;
@ -105,6 +55,9 @@ export interface DescriptionsProps {
style?: React.CSSProperties;
bordered?: boolean;
size?: 'middle' | 'small' | 'default';
/**
* @deprecated use `items` instead
*/
children?: React.ReactNode;
title?: React.ReactNode;
extra?: React.ReactNode;
@ -113,6 +66,7 @@ export interface DescriptionsProps {
colon?: boolean;
labelStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
items?: DescriptionsItemType[];
}
const Descriptions: React.FC<DescriptionsProps> & CompoundedComponent = (props) => {
@ -131,6 +85,7 @@ const Descriptions: React.FC<DescriptionsProps> & CompoundedComponent = (props)
size: customizeSize,
labelStyle,
contentStyle,
items,
...restProps
} = props;
const { getPrefixCls, direction, descriptions } = React.useContext(ConfigContext);
@ -139,6 +94,7 @@ const Descriptions: React.FC<DescriptionsProps> & CompoundedComponent = (props)
const mergedColumn = getColumn(column, screens);
const mergedSize = useSize(customizeSize);
const rows = useRow(mergedColumn, items, children);
const [wrapSSR, hashId] = useStyle(prefixCls);
const responsiveObserver = useResponsiveObserver();
@ -157,8 +113,7 @@ const Descriptions: React.FC<DescriptionsProps> & CompoundedComponent = (props)
};
}, []);
// Children
const rows = getRows(children, mergedColumn);
// ======================== Render ========================
const contextValue = React.useMemo(
() => ({ labelStyle, contentStyle }),
[labelStyle, contentStyle],

48
components/descriptions/index.zh-CN.md

@ -13,6 +13,52 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*d27AQJrowGAAAA
常见于详情页的信息展示。
```tsx | pure
// >= 5.8.0 可用,推荐的写法 ✅
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'UserName',
children: <p>Zhou Maomao</p>,
},
{
key: '2',
label: 'Telephone',
children: <p>1810000000</p>,
},
{
key: '3',
label: 'Live',
children: <p>Hangzhou, Zhejiang</p>,
},
{
key: '4',
label: 'Remark',
children: <p>empty</p>,
},
{
key: '5',
label: 'Address',
children: <p>No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China</p>,
},
];
<Descriptions title="User Info" items={items} />;
// <5.8.0 可用>=5.8.0 时不推荐 🙅🏻‍♀️
<Descriptions title="User Info">
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
<Descriptions.Item label="Telephone">1810000000</Descriptions.Item>
<Descriptions.Item label="Live">Hangzhou, Zhejiang</Descriptions.Item>
<Descriptions.Item label="Remark">empty</Descriptions.Item>
<Descriptions.Item label="Address">
No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China
</Descriptions.Item>
</Descriptions>;
```
## 代码演示
<!-- prettier-ignore -->
@ -24,6 +70,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*d27AQJrowGAAAA
<code src="./demo/vertical.tsx">垂直</code>
<code src="./demo/vertical-border.tsx">垂直带边框的</code>
<code src="./demo/style.tsx" debug>自定义 label & wrapper 样式</code>
<code src="./demo/jsx.tsx" debug>JSX demo</code>
<code src="./demo/component-token.tsx" debug>组件 Token</code>
## API
@ -37,6 +84,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*d27AQJrowGAAAA
| column | 一行的 `DescriptionItems` 数量,可以写成像素值或支持响应式的对象写法 `{ xs: 8, sm: 16, md: 24}` | number \| [Record<Breakpoint, number>](https://github.com/ant-design/ant-design/blob/84ca0d23ae52e4f0940f20b0e22eabe743f90dca/components/descriptions/index.tsx#L111C21-L111C56) | 3 | |
| contentStyle | 自定义内容样式 | CSSProperties | - | 4.10.0 |
| extra | 描述列表的操作区域,显示在右上方 | ReactNode | - | 4.5.0 |
| items | 描述列表项内容 | [DescriptionsItem](#descriptionitem)[] | - | 5.8.0 |
| labelStyle | 自定义标签样式 | CSSProperties | - | 4.10.0 |
| layout | 描述布局 | `horizontal` \| `vertical` | `horizontal` | |
| size | 设置列表的大小。可以设置为 `middle` 、`small`, 或不填(只有设置 `bordered={true}` 生效) | `default` \| `middle` \| `small` | - | |

25
components/modal/ConfirmDialog.tsx

@ -16,6 +16,12 @@ import type { ModalFuncProps, ModalLocale } from './interface';
interface ConfirmDialogProps extends ModalFuncProps {
afterClose?: () => void;
close?: (...args: any[]) => void;
/**
* `close` prop support `...args` that pass to the developer
* that we can not break this.
* Provider `onClose` for internal usage
*/
onConfirm?: (confirmed: boolean) => void;
autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls: string;
iconPrefixCls?: string;
@ -23,6 +29,11 @@ interface ConfirmDialogProps extends ModalFuncProps {
/** @private Internal Usage. Do not override this */
locale?: ModalLocale;
/**
* Do not throw if is await mode
*/
isSilent?: () => boolean;
}
export function ConfirmContent(
@ -35,6 +46,8 @@ export function ConfirmContent(
onCancel,
onOk,
close,
onConfirm,
isSilent,
okText,
okButtonProps,
cancelText,
@ -89,8 +102,12 @@ export function ConfirmContent(
const cancelButton = mergedOkCancel && (
<ActionButton
isSilent={isSilent}
actionFn={onCancel}
close={close}
close={(...args: any[]) => {
close?.(...args);
onConfirm?.(false);
}}
autoFocus={autoFocusButton === 'cancel'}
buttonProps={cancelButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
@ -112,9 +129,13 @@ export function ConfirmContent(
<div className={`${confirmPrefixCls}-btns`}>
{cancelButton}
<ActionButton
isSilent={isSilent}
type={okType}
actionFn={onOk}
close={close}
close={(...args: any[]) => {
close?.(...args);
onConfirm?.(true);
}}
autoFocus={autoFocusButton === 'ok'}
buttonProps={okButtonProps}
prefixCls={`${rootPrefixCls}-btn`}

44
components/modal/__tests__/hook.test.tsx

@ -367,4 +367,48 @@ describe('Modal.hook', () => {
expect(afterClose).toHaveBeenCalledTimes(1);
});
it('support await', async () => {
jest.useFakeTimers();
let notReady = true;
let lastResult: boolean | null = null;
const Demo = () => {
const [modal, contextHolder] = Modal.useModal();
React.useEffect(() => {
(async () => {
lastResult = await modal.confirm({
content: <Input />,
onOk: async () => {
if (notReady) {
notReady = false;
return Promise.reject();
}
},
});
})();
}, []);
return contextHolder;
};
render(<Demo />);
// Wait for modal show
await waitFakeTimer();
// First time click should not close
fireEvent.click(document.querySelector('.ant-btn-primary')!);
await waitFakeTimer();
expect(lastResult).toBeFalsy();
// Second time click to close
fireEvent.click(document.querySelector('.ant-btn-primary')!);
await waitFakeTimer();
expect(lastResult).toBeTruthy();
jest.useRealTimers();
});
});

4
components/modal/demo/hooks.md

@ -1,7 +1,7 @@
## zh-CN
通过 `Modal.useModal` 创建支持读取 context 的 `contextHolder`
通过 `Modal.useModal` 创建支持读取 context 的 `contextHolder`其中仅有 hooks 方法支持 Promise `await` 操作。
## en-US
Use `Modal.useModal` to get `contextHolder` with context accessible issue.
Use `Modal.useModal` to get `contextHolder` with context accessible issue. Only hooks method support Promise `await` operation.

9
components/modal/demo/hooks.tsx

@ -22,8 +22,9 @@ const App: React.FC = () => {
<ReachableContext.Provider value="Light">
<Space>
<Button
onClick={() => {
modal.confirm(config);
onClick={async () => {
const confirmed = await modal.confirm(config);
console.log('Confirmed: ', confirmed);
}}
>
Confirm
@ -36,14 +37,14 @@ const App: React.FC = () => {
Warning
</Button>
<Button
onClick={() => {
onClick={async () => {
modal.info(config);
}}
>
Info
</Button>
<Button
onClick={() => {
onClick={async () => {
modal.error(config);
}}
>

15
components/modal/index.en-US.md

@ -22,16 +22,16 @@ Additionally, if you need show a simple confirmation dialog, you can use [`App.u
<code src="./demo/basic.tsx">Basic</code>
<code src="./demo/async.tsx">Asynchronously close</code>
<code src="./demo/footer.tsx">Customized Footer</code>
<code src="./demo/confirm.tsx">Confirmation modal dialog</code>
<code src="./demo/hooks.tsx">Use hooks to get context</code>
<code src="./demo/locale.tsx">Internationalization</code>
<code src="./demo/manual.tsx">Manual to update destroy</code>
<code src="./demo/position.tsx">To customize the position of modal</code>
<code src="./demo/dark.tsx" debug>Dark Bg</code>
<code src="./demo/button-props.tsx">Customize footer buttons props</code>
<code src="./demo/hooks.tsx">Use hooks to get context</code>
<code src="./demo/modal-render.tsx">Custom modal content render</code>
<code src="./demo/width.tsx">To customize the width of modal</code>
<code src="./demo/static-info.tsx">Static Method</code>
<code src="./demo/confirm.tsx">Static confirmation</code>
<code src="./demo/confirm-router.tsx">destroy confirmation modal dialog</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/custom-mouse-position.tsx" debug>Control modal's animation origin position</code>
@ -167,6 +167,17 @@ React.useEffect(() => {
return <div>{contextHolder}</div>;
```
`modal.confirm` return method:
- `destroy`: Destroy current modal
- `update`: Update current modal
- `then`: (Hooks only) Promise chain call, support `await` operation
```tsx
// Return `true` when click `onOk` and `false` when click `onCancel`
const confirmed = await modal.confirm({ ... });
```
## Design Token
<ComponentTokenTable component="Modal"></ComponentTokenTable>

15
components/modal/index.zh-CN.md

@ -23,16 +23,16 @@ demo:
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/async.tsx">异步关闭</code>
<code src="./demo/footer.tsx">自定义页脚</code>
<code src="./demo/confirm.tsx">确认对话框</code>
<code src="./demo/hooks.tsx">使用 hooks 获得上下文</code>
<code src="./demo/locale.tsx">国际化</code>
<code src="./demo/manual.tsx">手动更新和移除</code>
<code src="./demo/position.tsx">自定义位置</code>
<code src="./demo/dark.tsx" debug>暗背景</code>
<code src="./demo/button-props.tsx">自定义页脚按钮属性</code>
<code src="./demo/hooks.tsx">使用 hooks 获得上下文</code>
<code src="./demo/modal-render.tsx">自定义渲染对话框</code>
<code src="./demo/width.tsx">自定义模态的宽度</code>
<code src="./demo/static-info.tsx">静态方法</code>
<code src="./demo/confirm.tsx">静态确认对话框</code>
<code src="./demo/confirm-router.tsx">销毁确认对话框</code>
<code src="./demo/render-panel.tsx" debug>\_InternalPanelDoNotUseOrYouWillBeFired</code>
<code src="./demo/custom-mouse-position.tsx" debug>控制弹框动画原点</code>
@ -168,6 +168,17 @@ React.useEffect(() => {
return <div>{contextHolder}</div>;
```
`modal.confirm` 返回方法:
- `destroy`:销毁当前窗口
- `update`:更新当前窗口
- `then`:Promise 链式调用,支持 `await` 操作。该方法为 Hooks 仅有
```tsx
//点击 `onOk` 时返回 `true`,点击 `onCancel` 时返回 `false`
const confirmed = await modal.confirm({ ... });
```
## Design Token
<ComponentTokenTable component="Modal"></ComponentTokenTable>

8
components/modal/useModal/HookModal.tsx

@ -8,6 +8,11 @@ import type { ModalFuncProps } from '../interface';
export interface HookModalProps {
afterClose: () => void;
config: ModalFuncProps;
onConfirm?: (confirmed: boolean) => void;
/**
* Do not throw if is await mode
*/
isSilent?: () => boolean;
}
export interface HookModalRef {
@ -16,7 +21,7 @@ export interface HookModalRef {
}
const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> = (
{ afterClose: hookAfterClose, config },
{ afterClose: hookAfterClose, config, ...restProps },
ref,
) => {
const [open, setOpen] = React.useState(true);
@ -66,6 +71,7 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
}
direction={innerConfig.direction || direction}
cancelText={innerConfig.cancelText || contextLocale?.cancelText}
{...restProps}
/>
);
};

35
components/modal/useModal/index.tsx

@ -1,6 +1,6 @@
import * as React from 'react';
import usePatchElement from '../../_util/hooks/usePatchElement';
import type { ModalStaticFunctions } from '../confirm';
import type { ModalFunc, ModalStaticFunctions } from '../confirm';
import { withConfirm, withError, withInfo, withSuccess, withWarn } from '../confirm';
import destroyFns from '../destroyFns';
import type { ModalFuncProps } from '../interface';
@ -13,6 +13,13 @@ interface ElementsHolderRef {
patchElement: ReturnType<typeof usePatchElement>[1];
}
// Add `then` field for `ModalFunc` return instance.
export type ModalFuncWithPromise = (...args: Parameters<ModalFunc>) => ReturnType<ModalFunc> & {
then<T>(resolve: (confirmed: boolean) => T, reject: VoidFunction): Promise<T>;
};
export type HookAPI = Omit<Record<keyof ModalStaticFunctions, ModalFuncWithPromise>, 'warn'>;
const ElementsHolder = React.memo(
React.forwardRef<ElementsHolderRef>((_props, ref) => {
const [elements, patchElement] = usePatchElement();
@ -28,10 +35,7 @@ const ElementsHolder = React.memo(
}),
);
function useModal(): readonly [
instance: Omit<ModalStaticFunctions, 'warn'>,
contextHolder: React.ReactElement,
] {
function useModal(): readonly [instance: HookAPI, contextHolder: React.ReactElement] {
const holderRef = React.useRef<ElementsHolderRef>(null);
// ========================== Effect ==========================
@ -56,6 +60,13 @@ function useModal(): readonly [
const modalRef = React.createRef<HookModalRef>();
// Proxy to promise with `onClose`
let resolvePromise: (confirmed: boolean) => void;
const promise = new Promise<boolean>((resolve) => {
resolvePromise = resolve;
});
let silent = false;
let closeFunc: Function | undefined;
const modal = (
<HookModal
@ -65,6 +76,10 @@ function useModal(): readonly [
afterClose={() => {
closeFunc?.();
}}
isSilent={() => silent}
onConfirm={(confirmed) => {
resolvePromise(confirmed);
}}
/>
);
@ -74,7 +89,7 @@ function useModal(): readonly [
destroyFns.push(closeFunc);
}
return {
const instance: ReturnType<ModalFuncWithPromise> = {
destroy: () => {
function destroyAction() {
modalRef.current?.destroy();
@ -97,12 +112,18 @@ function useModal(): readonly [
setActionQueue((prev) => [...prev, updateAction]);
}
},
then: (resolve) => {
silent = true;
return promise.then(resolve);
},
};
return instance;
},
[],
);
const fns = React.useMemo<Omit<ModalStaticFunctions, 'warn'>>(
const fns = React.useMemo<HookAPI>(
() => ({
info: getConfirmFunc(withInfo),
success: getConfirmFunc(withSuccess),

13
components/select/__tests__/index.test.tsx

@ -159,5 +159,18 @@ describe('Select', () => {
errSpy.mockRestore();
});
it('deprecate showArrow', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(<Select showArrow />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Select] `showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
expect(container.querySelector('.ant-select-show-arrow')).toBeTruthy();
errSpy.mockRestore();
});
});
});

1
components/select/demo/custom-tag-render.tsx

@ -26,7 +26,6 @@ const tagRender = (props: CustomTagProps) => {
const App: React.FC = () => (
<Select
mode="multiple"
showArrow
tagRender={tagRender}
defaultValue={['gold', 'cyan']}
style={{ width: '100%' }}

2
components/select/demo/search-box.tsx

@ -58,7 +58,7 @@ const SearchInput: React.FC<{ placeholder: string; style: React.CSSProperties }>
placeholder={props.placeholder}
style={props.style}
defaultActiveFirstOption={false}
showArrow={false}
suffixIcon={null}
filterOption={false}
onSearch={handleSearch}
onChange={handleChange}

3
components/select/index.en-US.md

@ -86,11 +86,10 @@ Select component to select value from options.
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| removeIcon | The custom remove icon | ReactNode | - | |
| searchValue | The current input "search" text | string | - | |
| showArrow | Whether to show the drop-down arrow | boolean | `true` | |
| showSearch | Whether select is searchable | boolean | single: false, multiple: true | |
| size | Size of Select input | `large` \| `middle` \| `small` | `middle` | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | The custom suffix icon. Customize icon will not response click open to avoid icon designed to do other interactive. You can use `pointer-events: none` style to bypass | ReactNode | - | |
| suffixIcon | The custom suffix icon. Customize icon will not response click open to avoid icon designed to do other interactive. You can use `pointer-events: none` style to bypass | ReactNode | `<DownOutlined />` | |
| tagRender | Customize tag render, only applies when `mode` is set to `multiple` or `tags` | (props) => ReactNode | - | |
| tokenSeparators | Separator used to tokenize, only applies when `mode="tags"` | string\[] | - | |
| value | Current selected option (considered as a immutable array) | string \| string\[] \| <br />number \| number\[] \| <br />LabeledValue \| LabeledValue\[] | - | |

22
components/select/index.tsx

@ -47,6 +47,11 @@ export interface InternalSelectProps<
disabled?: boolean;
mode?: 'multiple' | 'tags' | 'SECRET_COMBOBOX_MODE_DO_NOT_USE' | 'combobox';
bordered?: boolean;
/**
* @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a
* default behavior, you can hide it by setting `suffixIcon` to null.
*/
showArrow?: boolean;
}
export interface SelectProps<
@ -54,7 +59,7 @@ export interface SelectProps<
OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType,
> extends Omit<
InternalSelectProps<ValueType, OptionType>,
'inputIcon' | 'mode' | 'getInputElement' | 'getRawInputElement' | 'backfill' | 'placement'
'mode' | 'getInputElement' | 'getRawInputElement' | 'backfill' | 'placement'
> {
placement?: SelectCommonPlacement;
mode?: 'multiple' | 'tags';
@ -89,7 +94,6 @@ const InternalSelect = <
disabled: customDisabled,
notFoundContent,
status: customStatus,
showArrow,
builtinPlacements,
dropdownMatchSelectWidth,
popupMatchSelectWidth,
@ -133,7 +137,7 @@ const InternalSelect = <
}, [props.mode]);
const isMultiple = mode === 'multiple' || mode === 'tags';
const mergedShowArrow = useShowArrow(showArrow);
const showSuffixIcon = useShowArrow(props.suffixIcon, props.showArrow);
const mergedPopupMatchSelectWidth =
popupMatchSelectWidth ?? dropdownMatchSelectWidth ?? contextPopupMatchSelectWidth;
@ -163,8 +167,9 @@ const InternalSelect = <
multiple: isMultiple,
hasFeedback,
feedbackIcon,
showArrow: mergedShowArrow,
showSuffixIcon,
prefixCls,
showArrow: props.showArrow,
});
const selectProps = omit(props as typeof props & { itemIcon: React.ReactNode }, [
@ -226,6 +231,12 @@ const InternalSelect = <
'Select',
'`dropdownMatchSelectWidth` is deprecated. Please use `popupMatchSelectWidth` instead.',
);
warning(
!('showArrow' in props),
'Select',
'`showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
}
// ====================== Render =======================
@ -249,7 +260,7 @@ const InternalSelect = <
prefixCls={prefixCls}
placement={memoPlacement}
direction={direction}
inputIcon={suffixIcon}
suffixIcon={suffixIcon}
menuItemSelectedIcon={itemIcon}
removeIcon={removeIcon}
clearIcon={clearIcon}
@ -257,7 +268,6 @@ const InternalSelect = <
className={mergedClassName}
getPopupContainer={getPopupContainer || getContextPopupContainer}
dropdownClassName={rcSelectRtlDropdownClassName}
showArrow={hasFeedback || mergedShowArrow}
disabled={mergedDisabled}
/>,
);

3
components/select/index.zh-CN.md

@ -87,11 +87,10 @@ demo:
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
| searchValue | 控制搜索文本 | string | - | |
| showArrow | 是否显示下拉小箭头 | boolean | `true` | |
| showSearch | 配置是否可搜索 | boolean | 单选为 false,多选为 true | |
| size | 选择框大小 | `large` \| `middle` \| `small` | `middle` | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | 自定义的选择框后缀图标。以防止图标被用于其他交互,替换的图标默认不会响应展开、收缩事件,可以通过添加 `pointer-events: none` 样式透传。 | ReactNode | - | |
| suffixIcon | 自定义的选择框后缀图标。以防止图标被用于其他交互,替换的图标默认不会响应展开、收缩事件,可以通过添加 `pointer-events: none` 样式透传。 | ReactNode | `<DownOutlined />` | |
| tagRender | 自定义 tag 内容 render,仅在 `mode``multiple``tags` 时生效 | (props) => ReactNode | - | |
| tokenSeparators | 自动分词的分隔符,仅在 `mode="tags"` 时生效 | string\[] | - | |
| value | 指定当前选中的条目,多选时为一个数组。(value 数组引用未变化时,Select 不会更新) | string \| string\[] \| <br />number \| number\[] \| <br />LabeledValue \| LabeledValue\[] | - | |

8
components/select/useShowArrow.ts

@ -1,9 +1,11 @@
import type { ReactNode } from 'react';
/**
* Since Select, TreeSelect, Cascader is same Select like component.
* We just use same hook to handle this logic.
*
* If `showArrow` not configured, always show it.
* If `suffixIcon` is not equal to `null`, always show it.
*/
export default function useShowArrow(showArrow?: boolean) {
return showArrow ?? true;
export default function useShowArrow(suffixIcon?: ReactNode, showArrow?: boolean) {
return showArrow !== undefined ? showArrow : suffixIcon !== null;
}

21
components/select/utils/iconUtil.tsx

@ -18,8 +18,9 @@ export default function getIcons({
multiple,
hasFeedback,
prefixCls,
showArrow,
showSuffixIcon,
feedbackIcon,
showArrow,
}: {
suffixIcon?: React.ReactNode;
clearIcon?: RenderNode;
@ -30,18 +31,24 @@ export default function getIcons({
hasFeedback?: boolean;
feedbackIcon?: ReactNode;
prefixCls: string;
showSuffixIcon?: boolean;
showArrow?: boolean;
}) {
// Clear Icon
const mergedClearIcon = clearIcon ?? <CloseCircleFilled />;
// Validation Feedback Icon
const getSuffixIconNode = (arrowIcon?: ReactNode) => (
<>
{showArrow !== false && arrowIcon}
{hasFeedback && feedbackIcon}
</>
);
const getSuffixIconNode = (arrowIcon?: ReactNode) => {
if (suffixIcon === null && !hasFeedback && !showArrow) {
return null;
}
return (
<>
{showSuffixIcon !== false && arrowIcon}
{hasFeedback && feedbackIcon}
</>
);
};
// Arrow item icon
let mergedSuffixIcon = null;

13
components/tree-select/__tests__/index.test.tsx

@ -89,4 +89,17 @@ describe('TreeSelect', () => {
container.querySelector('.ant-select-tree-treenode-leaf-last')?.getAttribute('aria-label'),
).toBe('label');
});
it('deprecate showArrow', () => {
resetWarned();
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { container } = render(<TreeSelect showArrow />);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: TreeSelect] `showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
expect(container.querySelector('.ant-select-show-arrow')).toBeTruthy();
errSpy.mockRestore();
});
});

3
components/tree-select/index.en-US.md

@ -57,12 +57,11 @@ Tree selection control.
| placeholder | Placeholder of the select input | string | - | |
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| searchValue | Work with `onSearch` to make search value controlled | string | - | |
| showArrow | Whether to show the `suffixIcon` | boolean | `true` | |
| showCheckedStrategy | The way show selected item in box when `treeCheckable` set. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode) | `TreeSelect.SHOW_ALL` \| `TreeSelect.SHOW_PARENT` \| `TreeSelect.SHOW_CHILD` | `TreeSelect.SHOW_CHILD` | |
| showSearch | Support search or not | boolean | single: false \| multiple: true | |
| size | To set the size of the select input | `large` \| `middle` \| `small` | - | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | The custom suffix icon,you must set `showArrow` to `true` manually in multiple selection mode | ReactNode | - | |
| suffixIcon | The custom suffix icon | ReactNode | `<DownOutlined />` | |
| switcherIcon | Customize collapse/expand icon of tree node | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| tagRender | Customize tag render when `multiple` | (props) => ReactNode | - | |
| treeCheckable | Whether to show checkbox on the treeNodes | boolean | false | |

20
components/tree-select/index.tsx

@ -47,7 +47,6 @@ export interface TreeSelectProps<
RcTreeSelectProps<ValueType, OptionType>,
| 'showTreeIcon'
| 'treeMotion'
| 'inputIcon'
| 'mode'
| 'getInputElement'
| 'backfill'
@ -70,6 +69,11 @@ export interface TreeSelectProps<
/** @deprecated Please use `popupMatchSelectWidth` instead */
dropdownMatchSelectWidth?: boolean | number;
popupMatchSelectWidth?: boolean | number;
/**
* @deprecated `showArrow` is deprecated which will be removed in next major version. It will be a
* default behavior, you can hide it by setting `suffixIcon` to null.
*/
showArrow?: boolean;
}
const InternalTreeSelect = <
@ -98,7 +102,6 @@ const InternalTreeSelect = <
transitionName,
choiceTransitionName = '',
status: customStatus,
showArrow,
treeExpandAction,
builtinPlacements,
dropdownMatchSelectWidth,
@ -135,6 +138,12 @@ const InternalTreeSelect = <
'Select',
'`dropdownMatchSelectWidth` is deprecated. Please use `popupMatchSelectWidth` instead.',
);
warning(
!('showArrow' in props),
'TreeSelect',
'`showArrow` is deprecated which will be removed in next major version. It will be a default behavior, you can hide it by setting `suffixIcon` to null.',
);
}
const rootPrefixCls = getPrefixCls();
@ -157,7 +166,7 @@ const InternalTreeSelect = <
);
const isMultiple = !!(treeCheckable || multiple);
const mergedShowArrow = useShowArrow(showArrow);
const showSuffixIcon = useShowArrow(props.suffixIcon, props.showArrow);
const mergedPopupMatchSelectWidth =
popupMatchSelectWidth ?? dropdownMatchSelectWidth ?? contextPopupMatchSelectWidth;
@ -175,7 +184,7 @@ const InternalTreeSelect = <
const { suffixIcon, removeIcon, clearIcon } = getIcons({
...props,
multiple: isMultiple,
showArrow: mergedShowArrow,
showSuffixIcon,
hasFeedback,
feedbackIcon,
prefixCls,
@ -255,7 +264,7 @@ const InternalTreeSelect = <
treeCheckable ? <span className={`${prefixCls}-tree-checkbox-inner`} /> : treeCheckable
}
treeLine={!!treeLine}
inputIcon={suffixIcon}
suffixIcon={suffixIcon}
multiple={isMultiple}
placement={memoizedPlacement}
removeIcon={removeIcon}
@ -272,7 +281,6 @@ const InternalTreeSelect = <
getTransitionDirection(placement),
transitionName,
)}
showArrow={hasFeedback || mergedShowArrow}
treeExpandAction={treeExpandAction}
/>
);

3
components/tree-select/index.zh-CN.md

@ -58,12 +58,11 @@ demo:
| placeholder | 选择框默认文字 | string | - | |
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| searchValue | 搜索框的值,可以通过 `onSearch` 获取用户输入 | string | - | |
| showArrow | 是否显示 `suffixIcon` | boolean | `true` | |
| showCheckedStrategy | 配置 `treeCheckable` 时,定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点)。`TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时)。 默认只显示子节点 | `TreeSelect.SHOW_ALL` \| `TreeSelect.SHOW_PARENT` \| `TreeSelect.SHOW_CHILD` | `TreeSelect.SHOW_CHILD` | |
| showSearch | 是否支持搜索框 | boolean | 单选:false \| 多选:true | |
| size | 选择框大小 | `large` \| `middle` \| `small` | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| suffixIcon | 自定义的选择框后缀图标, 多选模式下必须同时设置 `showArrow` 为 true | ReactNode | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | `<DownOutlined />` | |
| switcherIcon | 自定义树节点的展开/折叠图标 | ReactNode \| ((props: AntTreeNodeProps) => ReactNode) | - | renderProps: 4.20.0 |
| tagRender | 自定义 tag 内容,多选时生效 | (props) => ReactNode | - | |
| treeCheckable | 显示 Checkbox | boolean | false | |

6
package.json

@ -122,7 +122,7 @@
"copy-to-clipboard": "^3.2.0",
"dayjs": "^1.11.1",
"qrcode.react": "^3.1.0",
"rc-cascader": "~3.12.0",
"rc-cascader": "~3.13.0",
"rc-checkbox": "~3.1.0",
"rc-collapse": "~3.7.0",
"rc-dialog": "~9.1.0",
@ -142,7 +142,7 @@
"rc-rate": "~2.12.0",
"rc-resize-observer": "^1.2.0",
"rc-segmented": "~2.2.0",
"rc-select": "~14.5.0",
"rc-select": "~14.6.0",
"rc-slider": "~10.1.0",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
@ -151,7 +151,7 @@
"rc-textarea": "~1.3.2",
"rc-tooltip": "~6.0.0",
"rc-tree": "~5.7.6",
"rc-tree-select": "~5.9.0",
"rc-tree-select": "~5.10.0",
"rc-upload": "~4.3.0",
"rc-util": "^5.32.0",
"scroll-into-view-if-needed": "^3.0.3",

Loading…
Cancel
Save