Skip to content

Commit 89d062c

Browse files
authored
Merge pull request #1 from react-component/suffix
feat: Support suffix
2 parents 1698ce8 + 36d92ce commit 89d062c

File tree

9 files changed

+264
-21
lines changed

9 files changed

+264
-21
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ yarn.lock
2929
package-lock.json
3030
coverage/
3131
.doc
32-
.umi
32+
.umi
33+
dist/

assets/index.less

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
display: flex;
55
flex-wrap: wrap;
66
max-width: 100%;
7+
position: relative;
78

89
&-item {
910
background: rgba(0, 255, 0, 0.2);
1011
box-shadow: 0 0 1px black;
1112
flex: none;
13+
max-width: 100%;
1214
}
1315
}

examples/fill-width.tsx

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import React from 'react';
2+
import Overflow from '../src';
3+
import '../assets/index.less';
4+
import './common.less';
5+
6+
interface ItemType {
7+
value: string | number;
8+
label: string;
9+
}
10+
11+
function createData(count: number): ItemType[] {
12+
const data: ItemType[] = new Array(count).fill(undefined).map((_, index) => ({
13+
value: index,
14+
label: `Label ${index}`,
15+
}));
16+
17+
return data;
18+
}
19+
20+
function renderItem(item: ItemType) {
21+
return (
22+
<div
23+
style={{
24+
margin: '0 16px 0 8px',
25+
padding: '4px 8px',
26+
background: 'rgba(255, 0, 0, 0.2)',
27+
}}
28+
>
29+
{item.label}
30+
</div>
31+
);
32+
}
33+
34+
function renderRest(items: ItemType[]) {
35+
return (
36+
<div
37+
style={{
38+
margin: '0 16px 0 8px',
39+
padding: '4px 8px',
40+
background: 'rgba(255, 0, 0, 0.2)',
41+
}}
42+
>
43+
+{items.length}...
44+
</div>
45+
);
46+
}
47+
48+
const inputStyle: React.CSSProperties = {
49+
border: 'none',
50+
fontSize: 12,
51+
margin: 0,
52+
outline: 'none',
53+
lineHeight: '20px',
54+
fontFamily: '-apple-system',
55+
padding: '0 4px',
56+
};
57+
58+
const Demo = () => {
59+
const [responsive, setResponsive] = React.useState(true);
60+
const [inputValue, setInputValue] = React.useState('');
61+
const [inputWidth, setInputWidth] = React.useState(0);
62+
const [data, setData] = React.useState(createData(3));
63+
const inputRef = React.useRef<HTMLInputElement>();
64+
const measureRef = React.useRef<HTMLDivElement>();
65+
66+
React.useLayoutEffect(() => {
67+
setInputWidth(measureRef.current.offsetWidth);
68+
}, [inputValue]);
69+
70+
React.useEffect(() => {
71+
inputRef.current.focus();
72+
}, []);
73+
74+
return (
75+
<div style={{ padding: 32 }}>
76+
<button
77+
type="button"
78+
onClick={() => {
79+
setResponsive(!responsive);
80+
}}
81+
>
82+
{responsive ? 'Responsive' : 'MaxCount: 6'}
83+
</button>
84+
<select
85+
style={{ width: 200, height: 32 }}
86+
value={data.length}
87+
onChange={({ target: { value } }) => {
88+
setData(createData(Number(value)));
89+
}}
90+
>
91+
<option value={0}>0</option>
92+
<option value={1}>1</option>
93+
<option value={2}>2</option>
94+
<option value={3}>3</option>
95+
<option value={5}>5</option>
96+
<option value={10}>10</option>
97+
<option value={20}>20</option>
98+
<option value={200}>200</option>
99+
</select>
100+
101+
<div
102+
style={{
103+
border: '5px solid green',
104+
padding: 8,
105+
maxWidth: 300,
106+
marginTop: 32,
107+
}}
108+
>
109+
<Overflow<ItemType>
110+
data={data}
111+
renderItem={renderItem}
112+
renderRest={renderRest}
113+
maxCount={responsive ? 'responsive' : 6}
114+
suffix={
115+
<div style={{ position: 'relative', maxWidth: '100%' }}>
116+
<input
117+
style={{
118+
...inputStyle,
119+
background: 'rgba(0, 0, 0, 0.1)',
120+
width: inputWidth,
121+
minWidth: 10,
122+
maxWidth: '100%',
123+
}}
124+
value={inputValue}
125+
onChange={e => {
126+
setInputValue(e.target.value);
127+
}}
128+
ref={inputRef}
129+
/>
130+
<div
131+
style={{
132+
...inputStyle,
133+
pointerEvents: 'none',
134+
position: 'absolute',
135+
left: 0,
136+
top: `200%`,
137+
}}
138+
ref={measureRef}
139+
>
140+
{inputValue}
141+
</div>
142+
</div>
143+
}
144+
/>
145+
</div>
146+
</div>
147+
);
148+
};
149+
150+
export default Demo;

now.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
{
66
"src": "package.json",
77
"use": "@now/static-build",
8-
"config": { "distDir": ".doc" }
8+
"config": { "distDir": "dist" }
99
}
1010
],
1111
"routes": [

src/Item.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface ItemProps<ItemType> {
66
prefixCls: string;
77
item?: ItemType;
88
className?: string;
9+
style?: React.CSSProperties;
910
renderItem?: (item: ItemType) => React.ReactNode;
1011
responsive?: boolean;
1112
itemKey?: React.Key;
@@ -24,6 +25,7 @@ export default function Item<ItemType>(props: ItemProps<ItemType>) {
2425
registerSize,
2526
itemKey,
2627
className,
28+
style,
2729
children,
2830
display,
2931
order,
@@ -52,9 +54,10 @@ export default function Item<ItemType>(props: ItemProps<ItemType>) {
5254
style={{
5355
opacity: mergedHidden ? 0.2 : 1,
5456
height: mergedHidden ? 0 : undefined,
55-
overflowY: responsive ? 'hidden' : undefined,
57+
overflowY: mergedHidden ? 'hidden' : undefined,
5658
order: responsive ? order : undefined,
5759
pointerEvents: mergedHidden ? 'none' : undefined,
60+
...style,
5861
}}
5962
>
6063
{childNode}

src/Overflow.tsx

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface OverflowProps<ItemType> {
2020
renderRest?:
2121
| React.ReactNode
2222
| ((omittedItems: ItemType[]) => React.ReactNode);
23+
suffix?: React.ReactNode;
2324
}
2425

2526
function defaultRenderRest<ItemType>(omittedItems: ItemType[]) {
@@ -40,6 +41,7 @@ function Overflow<ItemType = any>(
4041
className,
4142
maxCount,
4243
renderRest = defaultRenderRest,
44+
suffix,
4345
} = props;
4446

4547
const createUseState = useBatchFrameState();
@@ -51,14 +53,18 @@ function Overflow<ItemType = any>(
5153

5254
const [prevRestWidth, setPrevRestWidth] = createUseState(0);
5355
const [restWidth, setRestWidth] = createUseState(0);
54-
// Always use the max width to avoid blink
55-
const mergedRestWidth = Math.max(prevRestWidth, restWidth);
56+
57+
const [suffixWidth, setSuffixWidth] = createUseState(0);
58+
const [suffixFixedStart, setSuffixFixedStart] = useState<number>(null);
5659

5760
const [displayCount, setDisplayCount] = useState(0);
5861
const [restReady, setRestReady] = useState(false);
5962

6063
const itemPrefixCls = `${prefixCls}-item`;
6164

65+
// Always use the max width to avoid blink
66+
const mergedRestWidth = Math.max(prevRestWidth, restWidth);
67+
6268
// ================================= Data =================================
6369
const isResponsive = maxCount === RESPONSIVE;
6470

@@ -132,14 +138,18 @@ function Overflow<ItemType = any>(
132138
setPrevRestWidth(restWidth);
133139
}
134140

141+
function registerSuffixSize(_: React.Key, width: number | null) {
142+
setSuffixWidth(width!);
143+
}
144+
135145
// ================================ Effect ================================
136146
function getItemWidth(index: number) {
137147
return itemWidths.get(getKey(mergedData[index], index));
138148
}
139149

140150
React.useLayoutEffect(() => {
141151
if (containerWidth && mergedRestWidth && mergedData) {
142-
let totalWidth = 0;
152+
let totalWidth = suffixWidth;
143153

144154
const len = mergedData.length;
145155
const lastIndex = len - 1;
@@ -162,21 +172,48 @@ function Overflow<ItemType = any>(
162172
) {
163173
// Additional check if match the end
164174
updateDisplayCount(lastIndex);
175+
setSuffixFixedStart(null);
165176
break;
166177
} else if (totalWidth + mergedRestWidth > containerWidth) {
167178
// Can not hold all the content to show rest
168179
updateDisplayCount(i - 1);
180+
setSuffixFixedStart(
181+
totalWidth - currentItemWidth - suffixWidth + mergedRestWidth,
182+
);
169183
break;
170184
} else if (i === lastIndex) {
171185
// Reach the end
172186
updateDisplayCount(lastIndex);
187+
setSuffixFixedStart(totalWidth - suffixWidth);
173188
break;
174189
}
175190
}
191+
192+
if (suffix && getItemWidth(0) + suffixWidth > containerWidth) {
193+
setSuffixFixedStart(null);
194+
}
176195
}
177-
}, [containerWidth, itemWidths, mergedRestWidth, getKey, mergedData]);
196+
}, [
197+
containerWidth,
198+
itemWidths,
199+
mergedRestWidth,
200+
suffixWidth,
201+
getKey,
202+
mergedData,
203+
]);
178204

179205
// ================================ Render ================================
206+
const displayRest = restReady && !!omittedItems.length;
207+
208+
let suffixStyle: React.CSSProperties = {};
209+
if (suffixFixedStart !== null && isResponsive) {
210+
suffixStyle = {
211+
position: 'absolute',
212+
left: suffixFixedStart,
213+
top: 0,
214+
};
215+
}
216+
180217
let overflowNode = (
181218
<div className={classNames(prefixCls, className)} style={style} ref={ref}>
182219
{mergedData.map((item, index) => {
@@ -200,18 +237,33 @@ function Overflow<ItemType = any>(
200237
{/* Rest Count Item */}
201238
{showRest ? (
202239
<Item
203-
order={displayCount}
240+
order={displayRest ? displayCount : mergedData.length}
204241
prefixCls={itemPrefixCls}
205242
className={`${itemPrefixCls}-rest`}
206243
responsive={isResponsive}
207244
registerSize={registerOverflowSize}
208-
display={restReady && !!omittedItems.length}
245+
display={displayRest}
209246
>
210247
{typeof renderRest === 'function'
211248
? renderRest(omittedItems)
212249
: renderRest}
213250
</Item>
214251
) : null}
252+
253+
{/* Suffix Node */}
254+
{suffix && (
255+
<Item
256+
order={displayCount}
257+
prefixCls={itemPrefixCls}
258+
className={`${itemPrefixCls}-suffix`}
259+
responsive={isResponsive}
260+
registerSize={registerSuffixSize}
261+
display
262+
style={suffixStyle}
263+
>
264+
{suffix}
265+
</Item>
266+
)}
215267
</div>
216268
);
217269

0 commit comments

Comments
 (0)