diff --git a/assets/index.less b/assets/index.less
index cbd4a1309..e9c6e11e5 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -117,8 +117,20 @@
}
}
+ .@{select-prefix}-selection-overflow {
+ display: flex;
+ flex-wrap: wrap;
+ width: 100%;
+
+ &-item {
+ flex: none;
+ max-width: 100%;
+ }
+ }
+
.@{select-prefix}-selection-search {
position: relative;
+ max-width: 100%;
&-input,
&-mirror {
diff --git a/examples/tags.tsx b/examples/tags.tsx
index 20368ce26..c76dab474 100644
--- a/examples/tags.tsx
+++ b/examples/tags.tsx
@@ -14,10 +14,20 @@ for (let i = 10; i < 36; i += 1) {
const Test: React.FC = () => {
const [disabled, setDisabled] = React.useState(false);
- const [value, setValue] = React.useState(['name2', 'name3']);
- const [maxTagCount, setMaxTagCount] = React.useState(null);
+ const [value, setValue] = React.useState([
+ 'name1',
+ 'name2',
+ 'name3',
+ 'name4',
+ 'name5',
+ 'a10',
+ 'b11',
+ 'c12',
+ 'd13',
+ ]);
+ const [maxTagCount, setMaxTagCount] = React.useState('responsive');
- const toggleMaxTagCount = (count: number) => {
+ const toggleMaxTagCount = (count: number | 'responsive') => {
setMaxTagCount(count);
};
@@ -29,7 +39,7 @@ const Test: React.FC = () => {
tags select with open = false
diff --git a/package.json b/package.json
index 383e96bf0..fb9835ad4 100644
--- a/package.json
+++ b/package.json
@@ -46,17 +46,16 @@
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.0.1",
+ "rc-overflow": "^0.0.0-alpha.5",
"rc-trigger": "^5.0.4",
"rc-util": "^5.0.1",
- "rc-virtual-list": "^3.2.0",
- "warning": "^4.0.3"
+ "rc-virtual-list": "^3.2.0"
},
"devDependencies": {
"@types/enzyme": "^3.10.5",
"@types/jest": "^26.0.0",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.4",
- "@types/warning": "^3.0.0",
"cross-env": "^7.0.0",
"enzyme": "^3.3.0",
"enzyme-to-json": "^3.4.0",
diff --git a/src/Selector/MultipleSelector.tsx b/src/Selector/MultipleSelector.tsx
index b690d97ee..c8c3ff8cd 100644
--- a/src/Selector/MultipleSelector.tsx
+++ b/src/Selector/MultipleSelector.tsx
@@ -2,22 +2,26 @@ import * as React from 'react';
import { useState } from 'react';
import classNames from 'classnames';
import pickAttrs from 'rc-util/lib/pickAttrs';
-import { CSSMotionList } from 'rc-motion';
+import Overflow from 'rc-overflow';
import TransBtn from '../TransBtn';
-import { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator';
+import {
+ LabelValueType,
+ DisplayLabelValueType,
+ RawValueType,
+ CustomTagProps,
+ DefaultValueType,
+} from '../interface/generator';
import { RenderNode } from '../interface';
import { InnerSelectorProps } from '.';
import Input from './Input';
import useLayoutEffect from '../hooks/useLayoutEffect';
-const REST_TAG_KEY = '__RC_SELECT_MAX_REST_COUNT__';
-
interface SelectorProps extends InnerSelectorProps {
// Icon
removeIcon?: RenderNode;
// Tags
- maxTagCount?: number;
+ maxTagCount?: number | 'responsive';
maxTagTextLength?: number;
maxTagPlaceholder?: React.ReactNode | ((omittedValues: LabelValueType[]) => React.ReactNode);
tokenSeparators?: string[];
@@ -30,6 +34,10 @@ interface SelectorProps extends InnerSelectorProps {
onSelect: (value: RawValueType, option: { selected: boolean }) => void;
}
+const onPreventMouseDown = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+};
const SelectSelector: React.FC
= props => {
const {
id,
@@ -49,7 +57,6 @@ const SelectSelector: React.FC = props => {
tabIndex,
removeIcon,
- choiceTransitionName,
maxTagCount,
maxTagTextLength,
@@ -65,15 +72,11 @@ const SelectSelector: React.FC = props => {
onInputCompositionEnd,
} = props;
- const [motionAppear, setMotionAppear] = useState(false);
const measureRef = React.useRef(null);
const [inputWidth, setInputWidth] = useState(0);
const [focused, setFocused] = useState(false);
- // ===================== Motion ======================
- React.useEffect(() => {
- setMotionAppear(true);
- }, []);
+ const selectionPrefixCls = `${prefixCls}-selection`;
// ===================== Search ======================
const inputValue = open || mode === 'tags' ? searchValue : '';
@@ -84,21 +87,61 @@ const SelectSelector: React.FC = props => {
setInputWidth(measureRef.current.scrollWidth);
}, [inputValue]);
- // ==================== Selection ====================
- let displayValues: LabelValueType[] = values;
+ // ===================== Render ======================
+ // >>> Render Selector Node. Includes Item & Rest
+ function defaultRenderSelector(
+ content: React.ReactNode,
+ itemDisabled: boolean,
+ closable?: boolean,
+ onClose?: React.MouseEventHandler,
+ ) {
+ return (
+
+ {content}
+ {closable && (
+
+ ×
+
+ )}
+
+ );
+ }
- // Cut by `maxTagCount`
- let restCount: number;
- if (typeof maxTagCount === 'number') {
- restCount = values.length - maxTagCount;
- displayValues = values.slice(0, maxTagCount);
+ function customizeRenderSelector(
+ value: DefaultValueType,
+ content: React.ReactNode,
+ itemDisabled: boolean,
+ closable: boolean,
+ onClose: React.MouseEventHandler,
+ ) {
+ return (
+
+ {tagRender({
+ label: content,
+ value,
+ disabled: itemDisabled,
+ closable,
+ onClose,
+ })}
+
+ );
}
- // Update by `maxTagTextLength`
- if (typeof maxTagTextLength === 'number') {
- displayValues = displayValues.map(({ label, ...rest }) => {
- let displayLabel: React.ReactNode = label;
+ function renderItem({ disabled: itemDisabled, label, value }: DisplayLabelValueType) {
+ const closable = !disabled && !itemDisabled;
+
+ let displayLabel: React.ReactNode = label;
+ if (typeof maxTagTextLength === 'number') {
if (typeof label === 'string' || typeof label === 'number') {
const strLabel = String(displayLabel);
@@ -106,123 +149,87 @@ const SelectSelector: React.FC = props => {
displayLabel = `${strLabel.slice(0, maxTagTextLength)}...`;
}
}
+ }
- return {
- ...rest,
- label: displayLabel,
- };
- });
+ const onClose = (event?: React.MouseEvent) => {
+ if (event) event.stopPropagation();
+ onSelect(value, { selected: false });
+ };
+
+ return typeof tagRender === 'function'
+ ? customizeRenderSelector(value, displayLabel, itemDisabled, closable, onClose)
+ : defaultRenderSelector(displayLabel, itemDisabled, closable, onClose);
}
- // Fill rest
- if (restCount > 0) {
- displayValues.push({
- key: REST_TAG_KEY,
- label:
- typeof maxTagPlaceholder === 'function'
- ? maxTagPlaceholder(values.slice(maxTagCount))
- : maxTagPlaceholder,
- });
+ function renderRest(omittedValues: DisplayLabelValueType[]) {
+ const content =
+ typeof maxTagPlaceholder === 'function'
+ ? maxTagPlaceholder(omittedValues)
+ : maxTagPlaceholder;
+
+ return defaultRenderSelector(content, false);
}
- const selectionNode = (
- []}
- motionName={choiceTransitionName}
- motionAppear={motionAppear}
- >
- {({ key, label, value, disabled: itemDisabled, className, style }) => {
- const mergedKey = key || value;
- const closable = !disabled && key !== REST_TAG_KEY && !itemDisabled;
- const onMouseDown = (event: React.MouseEvent) => {
- event.preventDefault();
- event.stopPropagation();
- };
- const onClose = (event?: React.MouseEvent) => {
- if (event) event.stopPropagation();
- onSelect(value, { selected: false });
- };
-
- return typeof tagRender === 'function' ? (
-
- {tagRender({
- label,
- value,
- disabled: itemDisabled,
- closable,
- onClose,
- })}
-
- ) : (
-
- {label}
- {closable && (
-
- ×
-
- )}
-
- );
+ // >>> Input Node
+ const inputNode = (
+ {
+ setFocused(true);
+ }}
+ onBlur={() => {
+ setFocused(false);
}}
-
+ >
+
+
+ {/* Measure Node */}
+
+ {inputValue}
+
+
+ );
+
+ // >>> Selections
+ const selectionNode = (
+
);
return (
<>
{selectionNode}
- {
- setFocused(true);
- }}
- onBlur={() => {
- setFocused(false);
- }}
- >
-
-
- {/* Measure Node */}
-
- {inputValue}
-
-
-
{!values.length && !inputValue && (
- {placeholder}
+ {placeholder}
)}
>
);
diff --git a/src/Selector/index.tsx b/src/Selector/index.tsx
index ddc276d42..7c3350df5 100644
--- a/src/Selector/index.tsx
+++ b/src/Selector/index.tsx
@@ -69,7 +69,7 @@ export interface SelectorProps {
removeIcon?: RenderNode;
// Tags
- maxTagCount?: number;
+ maxTagCount?: number | 'responsive';
maxTagTextLength?: number;
maxTagPlaceholder?: React.ReactNode | ((omittedValues: LabelValueType[]) => React.ReactNode);
tagRender?: (props: CustomTagProps) => React.ReactElement;
diff --git a/src/generate.tsx b/src/generate.tsx
index 5a6d1cac1..a3b38ad71 100644
--- a/src/generate.tsx
+++ b/src/generate.tsx
@@ -129,7 +129,7 @@ export interface SelectProps extends Re
getInputElement?: () => JSX.Element;
optionLabelProp?: string;
maxTagTextLength?: number;
- maxTagCount?: number;
+ maxTagCount?: number | 'responsive';
maxTagPlaceholder?: React.ReactNode | ((omittedValues: LabelValueType[]) => React.ReactNode);
tokenSeparators?: string[];
tagRender?: (props: CustomTagProps) => React.ReactElement;
diff --git a/src/interface/generator.ts b/src/interface/generator.ts
index f56dad888..ccccea005 100644
--- a/src/interface/generator.ts
+++ b/src/interface/generator.ts
@@ -14,24 +14,18 @@ export interface LabelValueType {
value?: RawValueType;
label?: React.ReactNode;
}
-export type DefaultValueType =
- | RawValueType
- | RawValueType[]
- | LabelValueType
- | LabelValueType[];
+export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];
export interface DisplayLabelValueType extends LabelValueType {
disabled?: boolean;
}
-export type SingleType = MixType extends (infer Single)[]
- ? Single
- : MixType;
+export type SingleType = MixType extends (infer Single)[] ? Single : MixType;
export type OnClear = () => void;
export type CustomTagProps = {
- label: DefaultValueType;
+ label: React.ReactNode;
value: DefaultValueType;
disabled: boolean;
onClose: (event?: React.MouseEvent) => void;
@@ -59,19 +53,12 @@ export type FilterOptions = (
},
) => OptionsType;
-export type FilterFunc = (
- inputValue: string,
- option?: OptionType,
-) => boolean;
+export type FilterFunc = (inputValue: string, option?: OptionType) => boolean;
export declare function RefSelectFunc(
- Component: React.RefForwardingComponent<
- RefSelectProps,
- SelectProps
- >,
+ Component: React.RefForwardingComponent>,
): React.ForwardRefExoticComponent<
- React.PropsWithoutRef> &
- React.RefAttributes
+ React.PropsWithoutRef> & React.RefAttributes
>;
export type FlattenOptionsType = {
diff --git a/tests/__snapshots__/Multiple.test.tsx.snap b/tests/__snapshots__/Multiple.test.tsx.snap
index 03be8fbcd..d3f03dbbc 100644
--- a/tests/__snapshots__/Multiple.test.tsx.snap
+++ b/tests/__snapshots__/Multiple.test.tsx.snap
@@ -7,33 +7,42 @@ exports[`Select.Multiple render not display maxTagPlaceholder if maxTagCount not
+
@@ -48,86 +57,110 @@ exports[`Select.Multiple render truncates tags by maxTagCount and show maxTagPla
-
-
- One
-
-
- ×
+
+ One
+
+
+
+ ×
+
+
-
-
-
-
- Two
-
-
+
- ×
+
+ Two
+
+
+
+ ×
+
+
-
-
-
-
+
`;
@@ -139,86 +172,110 @@ exports[`Select.Multiple render truncates tags by maxTagCount and show maxTagPla
-
-
- One
-
-
- ×
+
+ One
+
+
+
+ ×
+
+
-
-
-
-
- Two
-
-
+
- ×
+
+ Two
+
+
+
+ ×
+
+
-
-
-
-
+
`;
@@ -230,75 +287,94 @@ exports[`Select.Multiple render truncates values by maxTagTextLength 1`] = `
-
-
- On...
-
-
- ×
+
+ On...
+
+
+
+ ×
+
+
-
-
-
-
- Tw...
-
-
+
`;
diff --git a/tests/__snapshots__/Tags.test.tsx.snap b/tests/__snapshots__/Tags.test.tsx.snap
index 592568685..1be6a1c65 100644
--- a/tests/__snapshots__/Tags.test.tsx.snap
+++ b/tests/__snapshots__/Tags.test.tsx.snap
@@ -8,73 +8,92 @@ exports[`Select.Tags OptGroup renders correctly 1`] = `
-
-
- Jack
-
-
- ×
+
+ Jack
+
+
+
+ ×
+
+
-
-
-
-
- foo
-
-
+
@@ -252,83 +280,107 @@ exports[`Select.Tags render truncates tags by maxTagCount and show maxTagPlaceho
-
-
- One
-
-
- ×
+
+ One
+
+
+
+ ×
+
+
-
-
-
-
- Two
-
-
+
- ×
+
+ Two
+
+
+
+ ×
+
+
-
-
-
-
+
`;
@@ -340,83 +392,107 @@ exports[`Select.Tags render truncates tags by maxTagCount and show maxTagPlaceho
-
-
- One
-
-
- ×
+
+ One
+
+
+
+ ×
+
+
-
-
-
-
- Two
-
-
+
- ×
+
+ Two
+
+
+
+ ×
+
+
-
-
-
-
+
`;
@@ -428,72 +504,91 @@ exports[`Select.Tags render truncates values by maxTagTextLength 1`] = `
-
-
- On...
-
-
- ×
+
+ On...
+
+
+
+ ×
+
+
-
-
-
-
- Tw...
-
-
+
`;