feat: add Space.Compact (#37652)

* feat: add Space.Compact

* feat: update input style

* feat: add CompactItem for context memoize

* feat: add demo

* feat: update button style

* feat: update input style

* feat: 提取 getCompactClassNames 公用方法

* feat: update button style

* feat: update picker

* feat: add block prop for Space.Compact

* feat: use Space.Compact for Input#addonBefore/After

* feat: update addon

* refactor: compact

* feat: update

* feat: update

* feat: migrate styles to compact for new Input/Input.Search

* feat: organize input demo

* feat: add more button compact demo

* feat: resolve select border collapse

* feat: fix input addon select style

* feat: add input addon demo in Space

* feat: add  useCompactItemContext hook

* feat: update compact-item style

* feat: rollback input#addon implements

* feat: rollback input#addon and input.search style

* feat: select border collapse

* feat: add Space.Compact demo

* feat: Space.Compact vertical for Button

* refactor: useCompactItemContext

* feat: update Button vertical demo

* feat: rtl for compact mixin

* feat: rtl for compact button

* feat: input rtl

* feat: add docs for Space.Compact

* test: add some tests for Space.Compact

* test: add tests

* feat: select style

* feat: handle select focus style

* feat: add useCompactItemContext for Picker and Cascader

* style: add compact-item style for cascader

* feat: support nested Space.Compact

* style: Input.Search in Space.Compact

* chore: clean

* feat: add useCompactItemContext for TreeSelect

* fix: lint issues

* fix: style lint

* docs: update demos for Space.Compact

* docs: update demo

* fix: add deps-lint-skip for space

* fix: add deps-lint-skip for space

* fix: handle edge case for useCompactItemContext

* test: add search test case

* chore: + bundlesize

* style: merge button compact style into one file

* style: improve style for nested compact

* fix: stylelint

* docs: update Space.Compact docs

* test: update demo snapshot

* test: update input debug test snapshot

* docs: update doccs for Space.Compact

* test: improve test cases for Compact

* docs: clean demos for Compact

* refactor: improve Space.Compact

* fix: add useCompactItemContext for Input.Search

* style: improve compact border-radius implementation

* refactor: use context to make nested compact works

* fix: input-number focused style

* refactor: remove useless style

* refactor: improve style for vertical compact

* test: update snapshot

* test: update snapshot for input

* refactor: improve compact-item-border-radius

* fix: search group in RTL

* style: improve style for button compact

* style: use after

* fix: stylelint

* chore: update snapshot

* style: improve button compact

* refactor: improve btn-icon-only style in compact
This commit is contained in:
Yuki Zhang 2022-10-18 16:23:10 +08:00 committed by GitHub
parent 84475b4ea5
commit c3e51506cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 16827 additions and 35 deletions

View File

@ -7,6 +7,7 @@ import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { useCompactItemContext } from '../space/Compact';
import { cloneElement } from '../_util/reactNode';
import { tuple } from '../_util/type';
import warning from '../_util/warning';
@ -170,7 +171,6 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
const buttonRef = (ref as any) || React.createRef<HTMLElement>();
const isNeedInserted = () =>
React.Children.count(children) === 1 && !icon && !isUnBorderedButtonType(type);
@ -240,9 +240,10 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const prefixCls = getPrefixCls('btn', customizePrefixCls);
const autoInsertSpace = autoInsertSpaceInButton !== false;
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
const sizeFullname = groupSize || customizeSize || size;
const sizeFullname = compactSize || groupSize || customizeSize || size;
const sizeCls = sizeFullname ? sizeClassNameMap[sizeFullname] || '' : '';
const iconType = innerLoading ? 'loading' : icon;
@ -264,6 +265,7 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-disabled`]: linkButtonRestProps.href !== undefined && mergedDisabled,
},
compactItemClassnames,
className,
);

View File

@ -289,4 +289,5 @@ a.@{btn-prefix-cls} {
}
}
@import './space-compact';
@import './rtl';

View File

@ -1,2 +1,4 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip: space

View File

@ -0,0 +1,88 @@
@import '../../style/mixins/index';
@btn-prefix-cls: ~'@{ant-prefix}-btn';
// Button in Space.Compact
.@{btn-prefix-cls} {
.compact-item(@btn-prefix-cls);
// make `btn-icon-only` not too narrow
&-icon-only&-compact-item {
flex: none;
}
// Special styles for Primary Button
&-compact-item.@{btn-prefix-cls}-primary {
&:not([disabled]) + &:not([disabled]) {
position: relative;
&::after {
position: absolute;
top: -@border-width-base;
left: -@border-width-base;
display: inline-block;
width: @border-width-base;
height: calc(100% + @border-width-base * 2);
background-color: @btn-group-border;
content: ' ';
}
}
}
// ----------RTL----------
&-compact-item-rtl {
&.@{btn-prefix-cls}-compact-first-item&:not(.@{btn-prefix-cls}-compact-last-item) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&.@{btn-prefix-cls}-compact-last-item&:not(.@{btn-prefix-cls}-compact-first-item) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&.@{btn-prefix-cls}-sm {
&.@{btn-prefix-cls}-compact-first-item&:not(.@{btn-prefix-cls}-compact-last-item) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&.@{btn-prefix-cls}-compact-last-item&:not(.@{btn-prefix-cls}-compact-first-item) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
// ----------RTL Special styles for Primary Button----------
&.@{btn-prefix-cls}-primary {
&:not([disabled]) + &:not([disabled]) {
&::after {
right: -@border-width-base;
}
}
}
}
// Button in Space.Compact when direction=vertical
.compact-item-vertical(@btn-prefix-cls);
// Special styles for Primary Button
&-compact-vertical-item {
&.@{btn-prefix-cls}-primary {
&:not([disabled]) + &:not([disabled]) {
position: relative;
&::after {
position: absolute;
top: -@border-width-base;
left: -@border-width-base;
display: inline-block;
width: calc(100% + @border-width-base * 2);
height: @border-width-base;
background-color: @btn-group-border;
content: ' ';
}
}
}
}
}

View File

@ -19,6 +19,8 @@ import defaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import DisabledContext from '../config-provider/DisabledContext';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { useCompactItemContext } from '../space/Compact';
import { FormItemInputContext } from '../form/context';
import getIcons from '../select/utils/iconUtil';
import type { SelectCommonPlacement } from '../_util/motion';
@ -187,7 +189,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
const rootPrefixCls = getPrefixCls();
const prefixCls = getPrefixCls('select', customizePrefixCls);
const cascaderPrefixCls = getPrefixCls('cascader', customizePrefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
// =================== Dropdown ====================
const mergedDropdownClassName = classNames(
popupClassName || dropdownClassName,
@ -219,7 +221,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
// ===================== Size ======================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
const mergedSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
@ -278,6 +280,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
compactItemClassnames,
className,
)}
disabled={mergedDisabled}

View File

@ -100,6 +100,9 @@
}
}
}
// ===================== Compact Item Styles =====================
.compact-item(@cascader-prefix-cls);
}
@import './rtl';

View File

@ -5,4 +5,4 @@ import './index.less';
import '../../empty/style';
import '../../select/style';
// deps-lint-skip: form
// deps-lint-skip: form, space

View File

@ -13,6 +13,7 @@ import { ConfigContext } from '../../config-provider';
import DisabledContext from '../../config-provider/DisabledContext';
import SizeContext from '../../config-provider/SizeContext';
import { FormItemInputContext } from '../../form/context';
import { useCompactItemContext } from '../../space/Compact';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import enUS from '../locale/en_US';
@ -54,6 +55,7 @@ export default function generateRangePicker<DateType>(
const innerRef = React.useRef<RCRangePicker<DateType>>(null);
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const { format, showTime, picker } = props as any;
const rootPrefixCls = getPrefixCls();
@ -72,7 +74,7 @@ export default function generateRangePicker<DateType>(
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
const mergedSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
@ -131,6 +133,7 @@ export default function generateRangePicker<DateType>(
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
compactItemClassnames,
className,
)}
locale={locale!.lang}

View File

@ -7,6 +7,7 @@ import type { GenerateConfig } from 'rc-picker/lib/generate/index';
import type { PickerMode } from 'rc-picker/lib/interface';
import * as React from 'react';
import { forwardRef, useContext, useImperativeHandle } from 'react';
import { useCompactItemContext } from '../../space/Compact';
import type { PickerDateProps, PickerProps, PickerTimeProps } from '.';
import { Components, getTimeProps } from '.';
import { ConfigContext } from '../../config-provider';
@ -54,6 +55,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
const { getPrefixCls, direction, getPopupContainer } = useContext(ConfigContext);
const prefixCls = getPrefixCls('picker', customizePrefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const innerRef = React.useRef<RCPicker<DateType>>(null);
const { format, showTime } = props as any;
@ -95,7 +97,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
);
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customizeSize || size;
const mergedSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
@ -145,6 +147,7 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
compactItemClassnames,
className,
)}
prefixCls={prefixCls}

View File

@ -357,6 +357,9 @@
}
}
}
// ===================== Compact Item Styles =====================
.compact-item(@picker-prefix-cls);
}
@import './panel';

View File

@ -4,4 +4,4 @@ import './index.less';
import '../../button/style';
import '../../tag/style';
// deps-lint-skip: form
// deps-lint-skip: form, space

View File

@ -11,6 +11,7 @@ import DisabledContext from '../config-provider/DisabledContext';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context';
import { useCompactItemContext } from '../space/Compact';
import { cloneElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
@ -52,6 +53,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
} = props;
const prefixCls = getPrefixCls('input-number', customizePrefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
let upIcon = <UpOutlined className={`${prefixCls}-handler-up-inner`} />;
let downIcon = <DownOutlined className={`${prefixCls}-handler-down-inner`} />;
const controlsTemp = typeof controls === 'boolean' ? controls : undefined;
@ -79,7 +81,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
} = useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const mergeSize = customizeSize || size;
const mergeSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
@ -93,6 +95,7 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props,
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
getStatusClassNames(prefixCls, mergedStatus),
compactItemClassnames,
className,
);

View File

@ -235,6 +235,9 @@
color: @error-color;
}
}
// ===================== Compact Item Styles =====================
.compact-item(@input-number-prefix-cls, null, ~'@{input-number-prefix-cls}-focused');
}
@import './rtl';

View File

@ -1,4 +1,4 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip: form
// deps-lint-skip: form, space

View File

@ -10,6 +10,7 @@ import DisabledContext from '../config-provider/DisabledContext';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context';
import { NoCompactStyle, useCompactItemContext } from '../space/Compact';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import warning from '../_util/warning';
@ -137,6 +138,7 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
allowClear,
addonAfter,
addonBefore,
className,
onChange,
...rest
} = props;
@ -145,9 +147,12 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
const prefixCls = getPrefixCls('input', customizePrefixCls);
const inputRef = useRef<InputRef>(null);
// ===================== Compact Item =====================
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
// ===================== Size =====================
const size = React.useContext(SizeContext);
const mergedSize = customSize || size;
const mergedSize = compactSize || customSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
@ -215,19 +220,24 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
onFocus={handleFocus}
suffix={suffixNode}
allowClear={mergedAllowClear}
className={classNames(className, compactItemClassnames)}
onChange={handleChange}
addonAfter={
addonAfter && (
<NoFormStyle override status>
{addonAfter}
</NoFormStyle>
<NoCompactStyle>
<NoFormStyle override status>
{addonAfter}
</NoFormStyle>
</NoCompactStyle>
)
}
addonBefore={
addonBefore && (
<NoFormStyle override status>
{addonBefore}
</NoFormStyle>
<NoCompactStyle>
<NoFormStyle override status>
{addonBefore}
</NoFormStyle>
</NoCompactStyle>
)
}
inputClassName={classNames(

View File

@ -5,6 +5,7 @@ import * as React from 'react';
import Button from '../button';
import { ConfigContext } from '../config-provider';
import SizeContext from '../config-provider/SizeContext';
import { useCompactItemContext } from '../space/Compact';
import { cloneElement } from '../_util/reactNode';
import type { InputProps, InputRef } from './Input';
import Input from './Input';
@ -44,7 +45,11 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
const contextSize = React.useContext(SizeContext);
const composedRef = React.useRef<boolean>(false);
const size = customizeSize || contextSize;
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const { compactSize } = useCompactItemContext(prefixCls, direction);
const size = compactSize || customizeSize || contextSize;
const inputRef = React.useRef<InputRef>(null);
@ -76,9 +81,6 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
onSearch(e);
};
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
const searchIcon = typeof enterButton === 'boolean' ? <SearchOutlined /> : null;
const btnClassName = `${prefixCls}-button`;

View File

@ -5086,6 +5086,142 @@ exports[`renders ./components/input/demo/borderless-debug.md extend context corr
</div>
`;
exports[`renders ./components/input/demo/debug-addon.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
Input addon Button:
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Submit
</span>
</button>
</span>
</span>
</span>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Submit
</span>
</button>
</span>
</span>
</span>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<br />
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<br />
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
Input addon Button icon:
</div>
<div
class="ant-space-item"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span
aria-label="setting"
class="anticon anticon-setting"
role="img"
>
<svg
aria-hidden="true"
data-icon="setting"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
/>
</svg>
</span>
</button>
</span>
</span>
</span>
</div>
</div>
`;
exports[`renders ./components/input/demo/focus.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"

View File

@ -1303,6 +1303,142 @@ exports[`renders ./components/input/demo/borderless-debug.md correctly 1`] = `
</div>
`;
exports[`renders ./components/input/demo/debug-addon.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
Input addon Button:
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Submit
</span>
</button>
</span>
</span>
</span>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
Submit
</span>
</button>
</span>
</span>
</span>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<br />
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<br />
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
Input addon Button icon:
</div>
<div
class="ant-space-item"
>
<span
class="ant-input-group-wrapper"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input"
type="text"
value="mysite"
/>
<span
class="ant-input-group-addon"
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span
aria-label="setting"
class="anticon anticon-setting"
role="img"
>
<svg
aria-hidden="true"
data-icon="setting"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
/>
</svg>
</span>
</button>
</span>
</span>
</span>
</div>
</div>
`;
exports[`renders ./components/input/demo/focus.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"

View File

@ -0,0 +1,42 @@
---
order: 100
title:
zh-CN: debug 前置/后置标签
en-US: debug Pre / Post tab
debug: true
---
## zh-CN
一些特殊的前置后置标签。
## en-US
Some special pre & post tabs example.
```tsx
import { SettingOutlined } from '@ant-design/icons';
import { Input, Space, Button } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Space direction="vertical">
Input addon Button:
<Input addonAfter={<Button type="primary">Submit</Button>} defaultValue="mysite" />
<Input addonAfter={<Button>Submit</Button>} defaultValue="mysite" />
<br />
<br />
Input addon Button icon:
<Input
addonAfter={
<Button>
<SettingOutlined />
</Button>
}
defaultValue="mysite"
/>
</Space>
);
export default App;
```

View File

@ -4,7 +4,6 @@
@import './affix';
@import './allow-clear';
@import './status';
@input-prefix-cls: ~'@{ant-prefix}-input';
// Input styles
@ -79,6 +78,9 @@
align-items: center;
margin: auto;
}
// ===================== Compact Item Styles =====================
.compact-item(@input-prefix-cls);
}
@import './search-input';

View File

@ -1,6 +1,6 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip: form
// deps-lint-skip: form, space
// style dependencies
import '../../button/style';

View File

@ -68,4 +68,50 @@
&-small &-button {
height: @input-height-sm;
}
// ===================== Compact Item Customized Styles =====================
&.@{input-prefix-cls}-compact-item {
&:not(.@{input-prefix-cls}-compact-item-rtl) {
&:not(.@{input-prefix-cls}-compact-last-item) {
.@{input-prefix-cls}-group-addon {
.@{input-prefix-cls}-search-button {
margin-right: -@border-width-base;
border-radius: 0;
}
}
}
}
&:not(.@{input-prefix-cls}-compact-first-item) {
.@{input-prefix-cls},
.@{input-prefix-cls}-affix-wrapper {
border-radius: 0;
}
}
> .@{input-prefix-cls}-group-addon .@{input-prefix-cls}-search-button,
> .@{input-prefix-cls},
.@{input-prefix-cls}-affix-wrapper {
&:hover,
&:focus,
&:active {
z-index: 2;
}
}
> .@{input-prefix-cls}-affix-wrapper-focused {
z-index: 2;
}
}
// ===================== For RTL Compact Item Customized Styles =====================
&.@{input-prefix-cls}-compact-item-rtl {
&:not(.@{input-prefix-cls}-compact-last-item) {
.@{input-prefix-cls}-group-addon:last-child {
.@{input-prefix-cls}-search-button {
margin-left: -@border-width-base;
border-radius: 0;
}
}
}
}
}

View File

@ -20,6 +20,7 @@ import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import getIcons from './utils/iconUtil';
import warning from '../_util/warning';
import { useCompactItemContext } from '../space/Compact';
type RawValue = string | number;
@ -96,6 +97,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
const prefixCls = getPrefixCls('select', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const mode = React.useMemo(() => {
const { mode: m } = props as InternalSelectProps<OptionType>;
@ -157,7 +159,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
[`${prefixCls}-dropdown-${direction}`]: direction === 'rtl',
});
const mergedSize = customizeSize || size;
const mergedSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
@ -172,6 +174,7 @@ const InternalSelect = <OptionType extends BaseOptionType | DefaultOptionType =
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
compactItemClassnames,
className,
);

View File

@ -325,6 +325,9 @@
&&-in-form-item {
width: 100%;
}
// ===================== Compact Item Styles =====================
.compact-item(@select-prefix-cls, ~'@{select-prefix-cls}-selector', ~'@{select-prefix-cls}-focused');
}
@import './rtl';

View File

@ -4,4 +4,4 @@ import './index.less';
// style dependencies
import '../../empty/style';
// deps-lint-skip: form
// deps-lint-skip: form, space

View File

@ -0,0 +1,123 @@
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import * as React from 'react';
import type { DirectionType } from '../config-provider';
import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
export interface SpaceCompactItemContextType {
compactSize?: SizeType;
compactDirection?: 'horizontal' | 'vertical';
isFirstItem?: boolean;
isLastItem?: boolean;
}
export const SpaceCompactItemContext = React.createContext<SpaceCompactItemContextType | null>(
null,
);
export const useCompactItemContext = (prefixCls: string, direction: DirectionType) => {
const compactItemContext = React.useContext(SpaceCompactItemContext);
const compactItemClassnames = React.useMemo(() => {
if (!compactItemContext) return '';
const { compactDirection, isFirstItem, isLastItem } = compactItemContext;
const separator = compactDirection === 'vertical' ? '-vertical-' : '-';
return classNames({
[`${prefixCls}-compact${separator}item`]: true,
[`${prefixCls}-compact${separator}first-item`]: isFirstItem,
[`${prefixCls}-compact${separator}last-item`]: isLastItem,
[`${prefixCls}-compact${separator}item-rtl`]: direction === 'rtl',
});
}, [prefixCls, direction, compactItemContext]);
return {
compactSize: compactItemContext?.compactSize,
compactDirection: compactItemContext?.compactDirection,
compactItemClassnames,
};
};
export const NoCompactStyle: React.FC<React.PropsWithChildren<{}>> = ({ children }) => (
<SpaceCompactItemContext.Provider value={null}>{children}</SpaceCompactItemContext.Provider>
);
export interface SpaceCompactProps extends React.HTMLAttributes<HTMLDivElement> {
prefixCls?: string;
size?: SizeType;
direction?: 'horizontal' | 'vertical';
block?: boolean;
}
const CompactItem: React.FC<React.PropsWithChildren<SpaceCompactItemContextType>> = ({
children,
...otherProps
}) => (
<SpaceCompactItemContext.Provider value={otherProps}>{children}</SpaceCompactItemContext.Provider>
);
const Compact: React.FC<SpaceCompactProps> = props => {
const { getPrefixCls, direction: directionConfig } = React.useContext(ConfigContext);
const {
size = 'middle',
direction,
block,
prefixCls: customizePrefixCls,
className,
children,
...restProps
} = props;
const prefixCls = getPrefixCls('space-compact', customizePrefixCls);
const clx = classNames(
prefixCls,
{
[`${prefixCls}-rtl`]: directionConfig === 'rtl',
[`${prefixCls}-block`]: block,
[`${prefixCls}-vertical`]: direction === 'vertical',
},
className,
);
const compactItemContext = React.useContext(SpaceCompactItemContext);
const childNodes = toArray(children);
const nodes = React.useMemo(
() =>
childNodes.map((child, i) => {
const key = (child && child.key) || `${prefixCls}-item-${i}`;
return (
<CompactItem
key={key}
compactSize={size}
compactDirection={direction}
isFirstItem={i === 0 && (!compactItemContext || compactItemContext?.isFirstItem)}
isLastItem={
i === childNodes.length - 1 && (!compactItemContext || compactItemContext?.isLastItem)
}
>
{child}
</CompactItem>
);
}),
[size, childNodes, compactItemContext],
);
// =========================== Render ===========================
if (childNodes.length === 0) {
return null;
}
return (
<div className={clx} {...restProps}>
{nodes}
</div>
);
};
export default Compact;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Space.Compact rtl render component should be rendered correctly in RTL direction 1`] = `null`;
exports[`Space.Compact rtl render component should be rendered correctly in RTL direction 2`] = `
<div
class="ant-space-compact ant-space-compact-rtl"
>
<button
class="ant-btn ant-btn-primary ant-btn-rtl ant-btn-compact-item ant-btn-compact-first-item ant-btn-compact-last-item ant-btn-compact-item-rtl"
type="button"
>
<span>
Submit
</span>
</button>
</div>
`;

View File

@ -0,0 +1,185 @@
/* eslint-disable no-console */
import React from 'react';
import Space from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render } from '../../../tests/utils';
import Input from '../../input';
import Button from '../../button';
import AutoComplete from '../../auto-complete';
import Cascader from '../../cascader';
import DatePicker from '../../date-picker';
import Select from '../../select';
import TimePicker from '../../time-picker';
import TreeSelect from '../../tree-select';
describe('Space.Compact', () => {
mountTest(Space.Compact);
mountTest(() => (
<Space.Compact>
<Button type="primary">Submit</Button>
</Space.Compact>
));
rtlTest(Space.Compact);
rtlTest(() => (
<Space.Compact>
<Button type="primary">Submit</Button>
</Space.Compact>
));
it('should render width empty children', () => {
const { container } = render(<Space.Compact />);
expect(container.children.length).toBe(0);
});
it('block className', () => {
const { container } = render(
<Space.Compact block>
<Input defaultValue="https://ant.design" />
<Button type="primary">Submit</Button>
</Space.Compact>,
);
expect(
container.querySelector('.ant-space-compact')?.classList.contains('ant-space-compact-block'),
).toBe(true);
});
it('compact-item className', () => {
const { container } = render(
<Space.Compact>
<Input defaultValue="https://ant.design" />
<Input.Search />
<Button type="primary">Submit</Button>
</Space.Compact>,
);
expect(
container.querySelector('.ant-input')?.classList.contains('ant-input-compact-item'),
).toBe(true);
expect(
container.querySelector('.ant-input-search')?.classList.contains('ant-input-compact-item'),
).toBe(true);
expect(
container.querySelector('.ant-input')?.classList.contains('ant-input-compact-first-item'),
).toBe(true);
expect(
container
.querySelector('.ant-btn-compact-item')
?.classList.contains('ant-btn-compact-last-item'),
).toBe(true);
});
[
{
name: 'Button',
component: Button,
targetCls: 'ant-btn',
expectClsPrefix: 'ant-btn',
},
{
name: 'AutoComplete',
component: AutoComplete,
targetCls: 'ant-select',
expectClsPrefix: 'ant-select',
},
{
name: 'Cascader',
component: Cascader,
targetCls: 'ant-cascader',
expectClsPrefix: 'ant-select',
},
{
name: 'DatePicker',
component: DatePicker,
targetCls: 'ant-picker',
expectClsPrefix: 'ant-picker',
},
{
name: 'Input',
component: Input,
targetCls: 'ant-input',
expectClsPrefix: 'ant-input',
},
{
name: 'Input.Search',
component: Input.Search,
targetCls: 'ant-input-search',
expectClsPrefix: 'ant-input',
},
{
name: 'Select',
component: Select,
targetCls: 'ant-select',
expectClsPrefix: 'ant-select',
},
{
name: 'TimePicker',
component: TimePicker,
targetCls: 'ant-picker',
expectClsPrefix: 'ant-picker',
},
{
name: 'TreeSelect',
component: TreeSelect,
targetCls: 'ant-select',
expectClsPrefix: 'ant-select',
},
].forEach(({ component, name, targetCls, expectClsPrefix }) => {
it(`compact-item for ${name}`, () => {
const { container } = render(
<Space.Compact>{React.createElement(component as any)}</Space.Compact>,
);
expect(container.querySelectorAll(`.${targetCls}`).length).toBe(1);
['compact-item', 'compact-first-item', 'compact-last-item'].forEach(suffix => {
expect(
container
.querySelector(`.${targetCls}`)
?.classList.contains([expectClsPrefix, suffix].join('-')),
).toBe(true);
});
});
});
it('size', () => {
const { container } = render(
<Space.Compact size="small">
<Input defaultValue="https://ant.design" />
<Button type="primary">Submit</Button>
</Space.Compact>,
);
expect(container.querySelector('.ant-input')?.classList.contains('ant-input-sm')).toBe(true);
expect(container.querySelector('.ant-btn')?.classList.contains('ant-btn-sm')).toBe(true);
});
it('direction=vertical', () => {
const { container } = render(
<Space.Compact size="small" direction="vertical">
<Button type="primary">Button 1</Button>
<Button type="primary">Button 2</Button>
<Button type="primary">Button 3</Button>
<Button type="primary">Button 4</Button>
</Space.Compact>,
);
expect(
container
.querySelector('.ant-space-compact')
?.classList.contains('ant-space-compact-vertical'),
).toBe(true);
expect(
container.querySelector('.ant-btn')?.classList.contains('ant-btn-compact-vertical-item'),
).toBe(true);
expect(
container
.querySelectorAll('.ant-btn')[0]
?.classList.contains('ant-btn-compact-vertical-first-item'),
).toBe(true);
expect(
container
.querySelectorAll('.ant-btn')[3]
?.classList.contains('ant-btn-compact-vertical-last-item'),
).toBe(true);
});
});

View File

@ -0,0 +1,42 @@
---
order: 10
version: 4.24.0
title:
zh-CN: 垂直方向紧凑布局
en-US: Vertical Compact Mode
---
## zh-CN
垂直方向的紧凑布局,目前仅支持 Button 组合。
## en-US
Vertical Mode for Space.Compact, support Button only.
```tsx
import { Button, Space } from 'antd';
import React from 'react';
const App: React.FC = () => (
<Space>
<Space.Compact direction="vertical">
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
</Space.Compact>
<Space.Compact direction="vertical">
<Button type="dashed">Button 1</Button>
<Button type="dashed">Button 2</Button>
<Button type="dashed">Button 3</Button>
</Space.Compact>
<Space.Compact direction="vertical">
<Button type="primary">Button 1</Button>
<Button type="primary">Button 2</Button>
<Button type="primary">Button 3</Button>
</Space.Compact>
</Space>
);
export default App;
```

View File

@ -0,0 +1,136 @@
---
order: 9
version: 4.24.0
title:
zh-CN: Button 紧凑布局
en-US: Button Compact Mode
---
## zh-CN
Button 组件紧凑排列的示例。
## en-US
Button component compact example.
```tsx
import {
DownloadOutlined,
EllipsisOutlined,
HeartOutlined,
LikeOutlined,
CommentOutlined,
StarOutlined,
ShareAltOutlined,
WarningOutlined,
MailOutlined,
MobileOutlined,
} from '@ant-design/icons';
import { Button, Menu, Dropdown, Space, Tooltip } from 'antd';
import React from 'react';
const App: React.FC = () => (
<div>
<Space.Compact block>
<Tooltip title="Like">
<Button icon={<LikeOutlined />} />
</Tooltip>
<Tooltip title="Comment">
<Button icon={<CommentOutlined />} />
</Tooltip>
<Tooltip title="Star">
<Button icon={<StarOutlined />} />
</Tooltip>
<Tooltip title="Heart">
<Button icon={<HeartOutlined />} />
</Tooltip>
<Tooltip title="Share">
<Button icon={<ShareAltOutlined />} />
</Tooltip>
<Tooltip title="Download">
<Button icon={<DownloadOutlined />} />
</Tooltip>
<Dropdown
placement="bottomRight"
overlay={
<Menu
items={[
{
key: '1',
label: 'Report',
icon: <WarningOutlined />,
},
{
key: '2',
label: 'Mail',
icon: <MailOutlined />,
},
{
key: '3',
label: 'Mobile',
icon: <MobileOutlined />,
},
]}
/>
}
trigger={['click']}
>
<Button icon={<EllipsisOutlined />} />
</Dropdown>
</Space.Compact>
<br />
<Space.Compact block>
<Button type="primary">Button 1</Button>
<Button type="primary">Button 2</Button>
<Button type="primary">Button 3</Button>
<Button type="primary">Button 4</Button>
<Tooltip title="Tooltip">
<Button type="primary" icon={<DownloadOutlined />} disabled />
</Tooltip>
<Tooltip title="Tooltip">
<Button type="primary" icon={<DownloadOutlined />} />
</Tooltip>
</Space.Compact>
<br />
<Space.Compact block>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button>Button 3</Button>
<Tooltip title="Tooltip">
<Button icon={<DownloadOutlined />} disabled />
</Tooltip>
<Tooltip title="Tooltip">
<Button icon={<DownloadOutlined />} />
</Tooltip>
<Button type="primary">Button 4</Button>
<Dropdown
placement="bottomRight"
overlay={
<Menu
items={[
{
key: '1',
label: '1st item',
},
{
key: '2',
label: '2nd item',
},
{
key: '3',
label: '3rd item',
},
]}
/>
}
trigger={['click']}
>
<Button type="primary" icon={<EllipsisOutlined />} />
</Dropdown>
</Space.Compact>
</div>
);
export default App;
```

View File

@ -0,0 +1,107 @@
---
order: 99
version: 4.24.0
title:
zh-CN: 调试 Input 前置/后置标签
en-US: Input addon debug
debug: true
---
## zh-CN
调试 Input 前置/后置标签。
## en-US
Input addon debug.
```tsx
import { SettingOutlined, CopyOutlined, DownloadOutlined } from '@ant-design/icons';
import { Cascader, Input, Select, Space, Button, Tooltip } from 'antd';
import React from 'react';
const { Option } = Select;
const selectBefore = (
<Select defaultValue="http://" className="select-before">
<Option value="http://">http://</Option>
<Option value="https://">https://</Option>
</Select>
);
const selectAfter = (
<Select defaultValue=".com" className="select-after">
<Option value=".com">.com</Option>
<Option value=".jp">.jp</Option>
<Option value=".cn">.cn</Option>
<Option value=".org">.org</Option>
</Select>
);
const App: React.FC = () => (
<Space direction="vertical">
<Space.Compact block>
<Button>default Button</Button>
<Button danger>danger Button</Button>
<Button type="dashed">dashed Button</Button>
<Button type="text">text Button</Button>
<Button type="link">Link Button</Button>
<Tooltip title="Tooltip">
<Button icon={<DownloadOutlined />} disabled />
</Tooltip>
</Space.Compact>
<br />
<Space.Compact>
<Button>Prefix</Button>
<Input addonBefore="http://" addonAfter=".com" defaultValue="mysite" />
<Button type="primary">Submit</Button>
</Space.Compact>
<Space.Compact>
<Input placeholder="prefix" />
<Input addonBefore={selectBefore} addonAfter={selectAfter} defaultValue="mysite" />
<Button icon={<CopyOutlined />} />
</Space.Compact>
<Space.Compact>
<Input.Search />
<Input.Search />
<Button icon={<CopyOutlined />} />
</Space.Compact>
<Space.Compact>
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
<Button type="primary">Submit</Button>
<Input placeholder="suffix" addonAfter={<SettingOutlined />} />
</Space.Compact>
<Space.Compact>
<Input addonBefore="http://" suffix=".com" defaultValue="mysite" />
<Button type="primary">Submit</Button>
</Space.Compact>
<Space.Compact>
<Button>Prefix</Button>
<Input
addonBefore={<Cascader placeholder="cascader" style={{ width: 150 }} />}
defaultValue="mysite"
/>
<Button type="primary">Submit</Button>
</Space.Compact>
</Space>
);
export default App;
```
```css
.select-before {
width: 90px;
}
.select-after {
width: 80px;
}
[data-theme='compact'] .select-before {
width: 71px;
}
[data-theme='compact'] .select-after {
width: 65px;
}
```

View File

@ -0,0 +1,116 @@
---
order: 99
version: 4.24.0
title:
zh-CN: 紧凑布局嵌套
en-US: Nested Space Compact
debug: true
---
## zh-CN
嵌套使用的紧凑布局
## en-US
Nested `Space.Compact`
```tsx
import { CopyOutlined, SearchOutlined } from '@ant-design/icons';
import { Button, Cascader, Input, InputNumber, Space, Select, TimePicker } from 'antd';
import React from 'react';
const { Option } = Select;
const App: React.FC = () => (
<>
<Space.Compact block>
<Space.Compact>
<Space.Compact>
<Input style={{ width: 90 }} placeholder="Typing..." />
<Button icon={<SearchOutlined />} />
</Space.Compact>
<Space.Compact>
<InputNumber defaultValue={12} />
<Select defaultValue="Option1">
<Option value="Option1">Opt1</Option>
<Option value="Option2">Opt2</Option>
</Select>
</Space.Compact>
</Space.Compact>
<Button type="primary">Separator</Button>
<Space.Compact>
<Space.Compact>
<Input.Search style={{ width: 110 }} placeholder="Search" />
<Button type="primary">Submit</Button>
</Space.Compact>
<Space.Compact>
<Input defaultValue="mysite" />
<Button icon={<CopyOutlined />} />
</Space.Compact>
</Space.Compact>
</Space.Compact>
<>
<br />
<Space.Compact block>
<Space.Compact>
<TimePicker />
<Button type="primary">Submit</Button>
</Space.Compact>
<Space.Compact>
<Cascader
options={[
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
]}
placeholder="Select Address"
/>
<Button type="primary">Submit</Button>
</Space.Compact>
</Space.Compact>
</>
</>
);
export default App;
```
```css
[data-theme='compact'] .select-before {
width: 71px;
}
[data-theme='compact'] .select-after {
width: 65px;
}
```

View File

@ -0,0 +1,256 @@
---
order: 8
version: 4.24.0
title:
zh-CN: 紧凑布局组合
en-US: Compact Mode for form component
---
## zh-CN
使用 Space.Compact 让表单组件之间紧凑连接且合并边框。
## en-US
Compact Mode for form component.
```tsx
import { CopyOutlined } from '@ant-design/icons';
import {
AutoComplete,
Button,
Cascader,
DatePicker,
Input,
InputNumber,
Select,
Space,
TimePicker,
Tooltip,
TreeSelect,
} from 'antd';
import React from 'react';
const { Option } = Select;
const { TreeNode } = TreeSelect;
const App: React.FC = () => (
<div className="site-space-compact-wrapper">
<Space.Compact block>
<Input style={{ width: '20%' }} defaultValue="0571" />
<Input style={{ width: '30%' }} defaultValue="26888888" />
</Space.Compact>
<br />
<Space.Compact block size="small">
<Input style={{ width: 'calc(100% - 200px)' }} defaultValue="https://ant.design" />
<Button type="primary">Submit</Button>
</Space.Compact>
<br />
<Space.Compact block>
<Input style={{ width: 'calc(100% - 200px)' }} defaultValue="https://ant.design" />
<Button type="primary">Submit</Button>
</Space.Compact>
<br />
<Space.Compact block>
<Input
style={{ width: 'calc(100% - 200px)' }}
defaultValue="git@github.com:ant-design/ant-design.git"
/>
<Tooltip title="copy git url">
<Button icon={<CopyOutlined />} />
</Tooltip>
</Space.Compact>
<br />
<Space.Compact block>
<Select defaultValue="Zhejiang">
<Option value="Zhejiang">Zhejiang</Option>
<Option value="Jiangsu">Jiangsu</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
</Space.Compact>
<br />
<Space.Compact block>
<Select mode="multiple" defaultValue="Zhejianggggg" style={{ width: '50%' }}>
<Option value="Zhejianggggg">Zhejianggggg</Option>
<Option value="Jiangsu">Jiangsu</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="Xihu District, Hangzhou" />
</Space.Compact>
<br />
<Space.Compact block>
<Input.Search style={{ width: '30%' }} defaultValue="0571" />
<Input.Search allowClear style={{ width: '50%' }} defaultValue="26888888" />
<Input.Search style={{ width: '20%' }} defaultValue="+1" />
</Space.Compact>
<br />
<Space.Compact block>
<Select defaultValue="Option1">
<Option value="Option1">Option1</Option>
<Option value="Option2">Option2</Option>
</Select>
<Input style={{ width: '50%' }} defaultValue="input content" />
<InputNumber defaultValue={12} />
</Space.Compact>
<br />
<Space.Compact block>
<Input style={{ width: '50%' }} defaultValue="input content" />
<DatePicker style={{ width: '50%' }} />
</Space.Compact>
<br />
<Space.Compact block>
<DatePicker.RangePicker style={{ width: '70%' }} />
<Input style={{ width: '30%' }} defaultValue="input content" />
<Button type="primary">查询</Button>
</Space.Compact>
<br />
<Space.Compact block>
<Input style={{ width: '30%' }} defaultValue="input content" />
<DatePicker.RangePicker style={{ width: '70%' }} />
</Space.Compact>
<br />
<Space.Compact block>
<Select defaultValue="Option1-1">
<Option value="Option1-1">Option1-1</Option>
<Option value="Option1-2">Option1-2</Option>
</Select>
<Select defaultValue="Option2-2">
<Option value="Option2-1">Option2-1</Option>
<Option value="Option2-2">Option2-2</Option>
</Select>
</Space.Compact>
<br />
<Space.Compact block>
<Select defaultValue="1">
<Option value="1">Between</Option>
<Option value="2">Except</Option>
</Select>
<Input style={{ width: 100, textAlign: 'center' }} placeholder="Minimum" />
<Input
className="site-input-split"
style={{
width: 30,
borderLeft: 0,
borderRight: 0,
pointerEvents: 'none',
}}
placeholder="~"
disabled
/>
<Input
className="site-input-right"
style={{
width: 100,
textAlign: 'center',
}}
placeholder="Maximum"
/>
</Space.Compact>
<br />
<Space.Compact block>
<Select defaultValue="Sign Up" style={{ width: '30%' }}>
<Option value="Sign Up">Sign Up</Option>
<Option value="Sign In">Sign In</Option>
</Select>
<AutoComplete
style={{ width: '70%' }}
placeholder="Email"
options={[{ value: 'text 1' }, { value: 'text 2' }]}
/>
</Space.Compact>
<br />
<Space.Compact block>
<TimePicker style={{ width: '70%' }} />
<Cascader
style={{ width: '70%' }}
options={[
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
]}
placeholder="Select Address"
/>
</Space.Compact>
<br />
<Space.Compact block>
<TimePicker.RangePicker />
<TreeSelect
showSearch
style={{ width: '60%' }}
value="leaf1"
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder="Please select"
allowClear
treeDefaultExpandAll
onChange={() => {}}
>
<TreeNode value="parent 1" title="parent 1">
<TreeNode value="parent 1-0" title="parent 1-0">
<TreeNode value="leaf1" title="leaf1" />
<TreeNode value="leaf2" title="leaf2" />
</TreeNode>
<TreeNode value="parent 1-1" title="parent 1-1">
<TreeNode value="leaf3" title={<b style={{ color: '#08c' }}>leaf3</b>} />
</TreeNode>
</TreeNode>
</TreeSelect>
<Button type="primary">Submit</Button>
</Space.Compact>
<br />
</div>
);
export default App;
```
```css
.site-space-compact-wrapper .site-input-split {
background-color: #fff;
}
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl) {
border-left-width: 0;
}
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl):hover,
.site-space-compact-wrapper .site-input-right:not(.ant-input-rtl):focus {
border-left-width: 1px;
}
.site-space-compact-wrapper .site-input-right.ant-input-rtl {
border-right-width: 0;
}
.site-space-compact-wrapper .site-input-right.ant-input-rtl:hover,
.site-space-compact-wrapper .site-input-right.ant-input-rtl:focus {
border-right-width: 1px;
}
```

View File

@ -1,5 +1,5 @@
---
order: 3
order: 5
title:
zh-CN: 自定义尺寸
en-US: Customize Size

View File

@ -1,5 +1,5 @@
---
order: 99
order: 7
title:
zh-CN: 分隔符
en-US: Split

View File

@ -1,5 +1,5 @@
---
order: 98
order: 6
title:
zh-CN: 自动换行
en-US: Wrap

View File

@ -10,7 +10,8 @@ Set components spacing.
## When To Use
Avoid components clinging together and set a unified space.
- Avoid components clinging together and set a unified space.
- Use Space.Compact when child form components are compactly connected and the border is collapsed.
## API
@ -25,3 +26,22 @@ Avoid components clinging together and set a unified space.
### Size
`'small' | 'middle' | 'large' | number`
### Space.Compact
Use Space.Compact when child form components are compactly connected and the border is collapsed. The supported components are
- Button
- AutoComplete
- Cascader
- DatePicker
- Input/Input.Search
- Select
- TimePicker
- TreeSelect
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| block | Option to fit width to its parent\'s width | boolean | false | 4.24.0 |
| direction | Set direction of layout | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
| size | Set child component size | `large` \| `middle` \| `small` | `middle` | 4.24.0 |

View File

@ -5,6 +5,7 @@ import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import useFlexGapSupport from '../_util/hooks/useFlexGapSupport';
import Item from './Item';
import Compact from './Compact';
export const SpaceContext = React.createContext({
latestIndex: 0,
@ -145,4 +146,11 @@ const Space: React.FC<SpaceProps> = props => {
);
};
export default Space;
interface CompoundedComponent extends React.FC<SpaceProps> {
Compact: typeof Compact;
}
const CompoundedSpace = Space as CompoundedComponent;
CompoundedSpace.Compact = Compact;
export default CompoundedSpace;

View File

@ -15,9 +15,12 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
- 适合行内元素的水平间距。
- 可以设置各种水平对齐方式。
- 需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact自 antd@4.24.0 版本开始提供该组件。)。
## API
### Space
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 对齐方式 | `start` \| `end` \|`center` \|`baseline` | - | 4.2.0 |
@ -29,3 +32,24 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/wc6%263gJ0Y8/Space.svg
### Size
`'small' | 'middle' | 'large' | number`
### Space.Compact
> 自 antd@4.24.0 版本开始提供该组件。
需要表单组件之间紧凑连接且合并边框时,使用 Space.Compact。支持的组件有
- Button
- AutoComplete
- Cascader
- DatePicker
- Input/Input.Search
- Select
- TimePicker
- TreeSelect
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| block | 将宽度调整为父元素宽度的选项 | boolean | false | 4.24.0 |
| direction | 指定排列方向 | `vertical` \| `horizontal` | `horizontal` | 4.24.0 |
| size | 子组件大小 | `large` \| `middle` \| `small` | `middle` | 4.24.0 |

View File

@ -0,0 +1,17 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@space-compact-prefix-cls: ~'@{ant-prefix}-space-compact';
.@{space-compact-prefix-cls} {
display: inline-flex;
&-block {
display: flex;
width: 100%;
}
&-vertical {
flex-direction: column;
}
}

View File

@ -36,4 +36,5 @@
}
}
@import './compact';
@import './rtl';

View File

@ -7,4 +7,8 @@
&-rtl {
direction: rtl;
}
&-compact-rtl {
direction: rtl;
}
}

View File

@ -0,0 +1,51 @@
.compact-item-vertical-border-radius(@prefix-cls) {
&-item:not(&-first-item):not(&-last-item) {
border-radius: 0;
}
&-item&-first-item {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
.@{prefix-cls}-sm & {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
}
&-item&-last-item {
border-top-left-radius: 0;
border-top-right-radius: 0;
.@{prefix-cls}-sm & {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
}
.compact-item-vertical-border(@prefix-cls) {
// border collapse
&-item:not(&-last-item) {
margin-bottom: -@border-width-base;
}
&-item {
&:hover,
&:focus,
&:active {
z-index: 2;
}
&[disabled] {
z-index: 0;
}
}
}
.compact-item-vertical(@prefix-cls) {
&-compact-vertical {
.compact-item-vertical-border(@prefix-cls);
.compact-item-vertical-border-radius(@prefix-cls);
}
}

View File

@ -0,0 +1,170 @@
.compact-item-border-radius(@prefix-cls, @bordered-item-cls: null) {
& when (@bordered-item-cls = null) {
// border-radius
&-item:not(&-first-item):not(&-last-item).@{prefix-cls} {
border-radius: 0;
}
&-item.@{prefix-cls}&-first-item:not(&-item-rtl) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.@{prefix-cls}-sm & {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
&-item.@{prefix-cls}&-last-item:not(&-item-rtl) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.@{prefix-cls}-sm & {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
// ----------rtl for first item----------
&-item.@{prefix-cls}&-item-rtl&-first-item {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.@{prefix-cls}-sm & {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
// ----------rtl for last item----------
&-item.@{prefix-cls}&-item-rtl&-last-item {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.@{prefix-cls}-sm & {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}
& when (not (@bordered-item-cls = null)) {
// border-radius
&-item:not(&-first-item):not(&-last-item).@{prefix-cls} > .@{bordered-item-cls} {
border-radius: 0;
}
&-item&-first-item.@{prefix-cls}:not(&-item-rtl) > .@{bordered-item-cls} {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.@{prefix-cls}-sm & {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
&-item&-last-item.@{prefix-cls}:not(&-item-rtl) > .@{bordered-item-cls} {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.@{prefix-cls}-sm & {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
// ----------rtl for first item----------
&-item.@{prefix-cls}&-first-item&-item-rtl > .@{bordered-item-cls} {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.@{prefix-cls}-sm & {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
// ----------rtl for last item----------
&-item.@{prefix-cls}&-last-item&-item-rtl > .@{bordered-item-cls} {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
.@{prefix-cls}-sm & {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}
}
.compact-item-border(@prefix-cls, @bordered-item-cls: null, @special-open-cls) {
& when (@bordered-item-cls = null) {
// border collapse
&-item:not(&-last-item):not(&-item-rtl) {
margin-right: -@border-width-base;
}
// rtl border collapse
&-item:not(&-last-item)&-item-rtl {
margin-left: -@border-width-base;
}
&-item {
&:hover,
&:focus,
&:active {
z-index: 2;
}
// Select has an extra focus className
& when (not (@special-item-cls = null)) {
&.@{special-item-cls} {
z-index: 2;
}
}
&[disabled] {
z-index: 0;
}
}
}
& when (not (@bordered-item-cls = null)) {
// border collapse
&-item:not(&-last-item) {
margin-right: -@border-width-base;
&.@{prefix-cls}-compact-item-rtl {
margin-right: 0;
margin-left: -@border-width-base;
}
}
&-item {
&:hover,
&:focus,
&:active {
> * {
z-index: 2;
}
}
// Select has an special focus-item
& when (not (@special-item-cls = null)) {
&.@{special-item-cls} > * {
z-index: 2;
}
}
&[disabled] > * {
z-index: 0;
}
}
}
}
.compact-item(@prefix-cls, @bordered-item-cls: null, @special-item-cls: null) {
&-compact {
.compact-item-border(@prefix-cls, @bordered-item-cls, @special-item-cls);
.compact-item-border-radius(@prefix-cls, @bordered-item-cls);
}
}

View File

@ -12,3 +12,5 @@
@import 'reset';
@import 'operation-unit';
@import 'rounded-arrow';
@import 'compact-item';
@import 'compact-item-vertical';

View File

@ -20,6 +20,7 @@ import type { SelectCommonPlacement } from '../_util/motion';
import { getTransitionDirection, getTransitionName } from '../_util/motion';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { useCompactItemContext } from '../space/Compact';
import warning from '../_util/warning';
type RawValue = string | number;
@ -115,6 +116,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
const prefixCls = getPrefixCls('select', customizePrefixCls);
const treePrefixCls = getPrefixCls('select-tree', customizePrefixCls);
const treeSelectPrefixCls = getPrefixCls('tree-select', customizePrefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const mergedDropdownClassName = classNames(
popupClassName || dropdownClassName,
@ -173,7 +175,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
: ('bottomLeft' as SelectCommonPlacement);
};
const mergedSize = customizeSize || size;
const mergedSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
@ -188,6 +190,7 @@ const InternalTreeSelect = <OptionType extends BaseOptionType | DefaultOptionTyp
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
compactItemClassnames,
className,
);
const rootPrefixCls = getPrefixCls();

View File

@ -2,6 +2,6 @@ import '../../style/index.less';
import './index.less';
// style dependencies
// deps-lint-skip: tree, form
// deps-lint-skip: tree, form, space
import '../../empty/style';
import '../../select/style';

View File

@ -333,15 +333,15 @@
},
{
"path": "./dist/antd.min.css",
"maxSize": "66 kB"
"maxSize": "66.5 kB"
},
{
"path": "./dist/antd.dark.min.css",
"maxSize": "67.5 kB"
"maxSize": "68 kB"
},
{
"path": "./dist/antd.compact.min.css",
"maxSize": "66 kB"
"maxSize": "66.5 kB"
},
{
"path": "./dist/antd.variable.min.css",