From c159d0db908bed014071fcd46ac09d79bee328ce Mon Sep 17 00:00:00 2001
From: zombiej
Date: Mon, 14 Dec 2020 11:12:16 +0800
Subject: [PATCH 1/4] feat: Support responsive
---
assets/index.less | 12 +++
examples/tags.tsx | 21 +++-
package.json | 5 +-
src/Selector/MultipleSelector.tsx | 174 ++++++++++++++++++++++--------
src/Selector/index.tsx | 2 +-
src/generate.tsx | 8 +-
6 files changed, 165 insertions(+), 57 deletions(-)
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..6716d6028 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.4",
"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..c30c1b304 100644
--- a/src/Selector/MultipleSelector.tsx
+++ b/src/Selector/MultipleSelector.tsx
@@ -2,9 +2,15 @@ import * as React from 'react';
import { useState } from 'react';
import classNames from 'classnames';
import pickAttrs from 'rc-util/lib/pickAttrs';
+import Overflow from 'rc-overflow';
import { CSSMotionList } from 'rc-motion';
import TransBtn from '../TransBtn';
-import { LabelValueType, RawValueType, CustomTagProps } from '../interface/generator';
+import {
+ LabelValueType,
+ DisplayLabelValueType,
+ RawValueType,
+ CustomTagProps,
+} from '../interface/generator';
import { RenderNode } from '../interface';
import { InnerSelectorProps } from '.';
import Input from './Input';
@@ -17,7 +23,7 @@ interface SelectorProps extends InnerSelectorProps {
removeIcon?: RenderNode;
// Tags
- maxTagCount?: number;
+ maxTagCount?: number | 'responsive';
maxTagTextLength?: number;
maxTagPlaceholder?: React.ReactNode | ((omittedValues: LabelValueType[]) => React.ReactNode);
tokenSeparators?: string[];
@@ -70,6 +76,8 @@ const SelectSelector: React.FC
= props => {
const [inputWidth, setInputWidth] = useState(0);
const [focused, setFocused] = useState(false);
+ const selectionPrefixCls = `${prefixCls}-selection`;
+
// ===================== Motion ======================
React.useEffect(() => {
setMotionAppear(true);
@@ -120,12 +128,12 @@ const SelectSelector: React.FC = props => {
key: REST_TAG_KEY,
label:
typeof maxTagPlaceholder === 'function'
- ? maxTagPlaceholder(values.slice(maxTagCount))
+ ? maxTagPlaceholder(values.slice(maxTagCount as any))
: maxTagPlaceholder,
});
}
- const selectionNode = (
+ const selectionNode1 = (
[]}
@@ -157,15 +165,15 @@ const SelectSelector: React.FC = props => {
) : (
- {label}
+ {label}
{closable && (
= props => {
);
- return (
- <>
- {selectionNode}
+ function renderSelectorNode(childNode: React.ReactNode, itemDisabled?: boolean) {
+ if (typeof tagRender === 'function') {
+ // TODO: handle this
+ }
+ return (
{
- setFocused(true);
- }}
- onBlur={() => {
- setFocused(false);
- }}
+ className={classNames(`${selectionPrefixCls}-item`, {
+ [`${selectionPrefixCls}-item-disabled`]: itemDisabled,
+ })}
>
-
-
- {/* Measure Node */}
-
- {inputValue}
-
+ {childNode}
+
+ );
+ }
+
+ function renderItem(item: DisplayLabelValueType) {
+ const { label, value, disabled: itemDisabled } = item;
+ const closable = !disabled && !itemDisabled;
+
+ const onMouseDown = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+ };
+ const onClose = (event?: React.MouseEvent) => {
+ if (event) event.stopPropagation();
+ onSelect(value, { selected: false });
+ };
+
+ return renderSelectorNode(
+ <>
+ {label}
+ {closable && (
+
+ ×
+
+ )}
+ >,
+ itemDisabled,
+ );
+ }
+
+ function renderRest(omittedValues: DisplayLabelValueType[]) {
+ return renderSelectorNode(
+ typeof maxTagPlaceholder === 'function'
+ ? maxTagPlaceholder(omittedValues)
+ : maxTagPlaceholder,
+ );
+ }
+
+ // ===================== Render ======================
+ console.log('>>>', inputWidth);
+
+ // >>> Input Node
+ const inputNode = (
+ {
+ setFocused(true);
+ }}
+ onBlur={() => {
+ setFocused(false);
+ }}
+ >
+
+
+ {/* Measure Node */}
+
+ {inputValue}
+
+ );
+
+ // >>> Selections
+ const selectionNode = (
+
+ );
+
+ return (
+ <>
+ {selectionNode}
{!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..2ea931f2f 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;
@@ -192,7 +192,7 @@ export interface GenerateConfig {
getLabeledValue: GetLabeledValue>;
filterOptions: FilterOptions;
findValueOption: // Need still support legacy ts api
- | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType)
+ | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType)
// New API add prevValueOptions support
| ((
values: RawValueType[],
@@ -714,7 +714,9 @@ export default function generateSelector<
// If menu is open, OptionList will take charge
// If mode isn't tags, press enter is not meaningful when you can't see any option
const onSearchSubmit = (searchText: string) => {
- const newRawValues = Array.from(new Set([...mergedRawValue, searchText]));
+ const newRawValues = Array.from(
+ new Set([...mergedRawValue, searchText]),
+ );
triggerChange(newRawValues);
newRawValues.forEach(newRawValue => {
triggerSelect(newRawValue, true, 'input');
From e0935aafd0700b788809b894989bb82af9b586d5 Mon Sep 17 00:00:00 2001
From: zombiej
Date: Mon, 14 Dec 2020 11:34:36 +0800
Subject: [PATCH 2/4] feat: Support responsive
---
src/Selector/MultipleSelector.tsx | 201 ++++++++++--------------------
src/generate.tsx | 6 +-
src/interface/generator.ts | 25 +---
3 files changed, 71 insertions(+), 161 deletions(-)
diff --git a/src/Selector/MultipleSelector.tsx b/src/Selector/MultipleSelector.tsx
index c30c1b304..c8c3ff8cd 100644
--- a/src/Selector/MultipleSelector.tsx
+++ b/src/Selector/MultipleSelector.tsx
@@ -3,21 +3,19 @@ import { useState } from 'react';
import classNames from 'classnames';
import pickAttrs from 'rc-util/lib/pickAttrs';
import Overflow from 'rc-overflow';
-import { CSSMotionList } from 'rc-motion';
import TransBtn from '../TransBtn';
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;
@@ -36,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,
@@ -55,7 +57,6 @@ const SelectSelector: React.FC = props => {
tabIndex,
removeIcon,
- choiceTransitionName,
maxTagCount,
maxTagTextLength,
@@ -71,18 +72,12 @@ 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);
const selectionPrefixCls = `${prefixCls}-selection`;
- // ===================== Motion ======================
- React.useEffect(() => {
- setMotionAppear(true);
- }, []);
-
// ===================== Search ======================
const inputValue = open || mode === 'tags' ? searchValue : '';
const inputEditable: boolean = mode === 'tags' || (showSearch && (open || focused));
@@ -92,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);
@@ -114,136 +149,26 @@ const SelectSelector: React.FC = props => {
displayLabel = `${strLabel.slice(0, maxTagTextLength)}...`;
}
}
-
- return {
- ...rest,
- label: displayLabel,
- };
- });
- }
-
- // Fill rest
- if (restCount > 0) {
- displayValues.push({
- key: REST_TAG_KEY,
- label:
- typeof maxTagPlaceholder === 'function'
- ? maxTagPlaceholder(values.slice(maxTagCount as any))
- : maxTagPlaceholder,
- });
- }
-
- const selectionNode1 = (
- []}
- 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 && (
-
- ×
-
- )}
-
- );
- }}
-
- );
-
- function renderSelectorNode(childNode: React.ReactNode, itemDisabled?: boolean) {
- if (typeof tagRender === 'function') {
- // TODO: handle this
}
- return (
-
- {childNode}
-
- );
- }
-
- function renderItem(item: DisplayLabelValueType) {
- const { label, value, disabled: itemDisabled } = item;
- const closable = !disabled && !itemDisabled;
-
- const onMouseDown = (event: React.MouseEvent) => {
- event.preventDefault();
- event.stopPropagation();
- };
const onClose = (event?: React.MouseEvent) => {
if (event) event.stopPropagation();
onSelect(value, { selected: false });
};
- return renderSelectorNode(
- <>
- {label}
- {closable && (
-
- ×
-
- )}
- >,
- itemDisabled,
- );
+ return typeof tagRender === 'function'
+ ? customizeRenderSelector(value, displayLabel, itemDisabled, closable, onClose)
+ : defaultRenderSelector(displayLabel, itemDisabled, closable, onClose);
}
function renderRest(omittedValues: DisplayLabelValueType[]) {
- return renderSelectorNode(
+ const content =
typeof maxTagPlaceholder === 'function'
? maxTagPlaceholder(omittedValues)
- : maxTagPlaceholder,
- );
- }
+ : maxTagPlaceholder;
- // ===================== Render ======================
- console.log('>>>', inputWidth);
+ return defaultRenderSelector(content, false);
+ }
// >>> Input Node
const inputNode = (
diff --git a/src/generate.tsx b/src/generate.tsx
index 2ea931f2f..a3b38ad71 100644
--- a/src/generate.tsx
+++ b/src/generate.tsx
@@ -192,7 +192,7 @@ export interface GenerateConfig {
getLabeledValue: GetLabeledValue>;
filterOptions: FilterOptions;
findValueOption: // Need still support legacy ts api
- | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType)
+ | ((values: RawValueType[], options: FlattenOptionsType) => OptionsType)
// New API add prevValueOptions support
| ((
values: RawValueType[],
@@ -714,9 +714,7 @@ export default function generateSelector<
// If menu is open, OptionList will take charge
// If mode isn't tags, press enter is not meaningful when you can't see any option
const onSearchSubmit = (searchText: string) => {
- const newRawValues = Array.from(
- new Set([...mergedRawValue, searchText]),
- );
+ const newRawValues = Array.from(new Set([...mergedRawValue, searchText]));
triggerChange(newRawValues);
newRawValues.forEach(newRawValue => {
triggerSelect(newRawValue, true, 'input');
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 = {
From 0c1b6981b50f46ffa39f7b1a71a5d18f99fec192 Mon Sep 17 00:00:00 2001
From: zombiej
Date: Tue, 15 Dec 2020 11:07:22 +0800
Subject: [PATCH 3/4] bump rc-overflow
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 6716d6028..fb9835ad4 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.0.1",
- "rc-overflow": "^0.0.0-alpha.4",
+ "rc-overflow": "^0.0.0-alpha.5",
"rc-trigger": "^5.0.4",
"rc-util": "^5.0.1",
"rc-virtual-list": "^3.2.0"
From 98828b41a9c9aeb20fcf4a8ac3daf136a64273e9 Mon Sep 17 00:00:00 2001
From: zombiej
Date: Tue, 22 Dec 2020 23:12:52 +0800
Subject: [PATCH 4/4] update snapshot
---
tests/__snapshots__/Multiple.test.tsx.snap | 516 +++++++++--------
tests/__snapshots__/Tags.test.tsx.snap | 625 ++++++++++++---------
2 files changed, 656 insertions(+), 485 deletions(-)
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...
-
-
+
`;