diff --git a/src/Overflow.tsx b/src/Overflow.tsx index de6241b..80fe872 100644 --- a/src/Overflow.tsx +++ b/src/Overflow.tsx @@ -39,9 +39,6 @@ export interface OverflowProps extends React.HTMLAttributes { /** @private This API may be refactor since not well design */ onVisibleChange?: (visibleCount: number) => void; - - /** When set to `full`, ssr will render full items by default and remove at client side */ - ssr?: 'full'; } function defaultRenderRest(omittedItems: ItemType[]) { @@ -59,7 +56,6 @@ function Overflow( renderRawItem, itemKey, itemWidth = 10, - ssr, style, className, maxCount, @@ -72,8 +68,6 @@ function Overflow( ...restProps } = props; - const fullySSR = ssr === 'full'; - const notifyEffectUpdate = useBatcher(); const [containerWidth, setContainerWidth] = useEffectState( @@ -104,7 +98,7 @@ function Overflow( const [displayCount, setDisplayCount] = useState(null); const mergedDisplayCount = React.useMemo(() => { - if (displayCount === null && fullySSR) { + if (displayCount === null) { return Number.MAX_SAFE_INTEGER; } @@ -134,9 +128,7 @@ function Overflow( let items = data; if (shouldResponsive) { - if (containerWidth === null && fullySSR) { - items = data; - } else { + if (containerWidth !== null) { items = data.slice( 0, Math.min(data.length, mergedContainerWidth / itemWidth), @@ -147,7 +139,14 @@ function Overflow( } return items; - }, [data, itemWidth, containerWidth, maxCount, shouldResponsive]); + }, [ + data, + itemWidth, + containerWidth, + maxCount, + shouldResponsive, + mergedContainerWidth, + ]); const omittedItems = useMemo(() => { if (shouldResponsive) { @@ -250,12 +249,7 @@ function Overflow( } for (let i = 0; i < len; i += 1) { - let currentItemWidth = getItemWidth(i); - - // Fully will always render - if (fullySSR) { - currentItemWidth = currentItemWidth || 0; - } + const currentItemWidth = getItemWidth(i); // Break since data not ready if (currentItemWidth === undefined) { @@ -301,8 +295,18 @@ function Overflow( // ================================ Render ================================ const displayRest = restReady && !!omittedItems.length; + const isResponsiveAndFirstRender = isResponsive && containerWidth === null; + const responsiveAndFirstRenderStyle: React.CSSProperties = { + maxWidth: 0, + padding: 0, + margin: 0, + borderWidth: 0, + overflowX: 'hidden', + }; - let suffixStyle: React.CSSProperties = {}; + let suffixStyle: React.CSSProperties = isResponsiveAndFirstRender + ? responsiveAndFirstRenderStyle + : {}; if (suffixFixedStart !== null && shouldResponsive) { suffixStyle = { position: 'absolute', @@ -316,12 +320,22 @@ function Overflow( responsive: shouldResponsive, component: itemComponent, invalidate, + style: isResponsiveAndFirstRender + ? responsiveAndFirstRenderStyle + : undefined, }; // >>>>> Choice render fun by `renderRawItem` const internalRenderItemNode = renderRawItem ? (item: ItemType, index: number) => { const key = getKey(item, index); + const isIdxCheckPass = index <= mergedDisplayCount; + // in responsive case, item's `display` can be set to `true` when: + // 1) at initial render; 2) its corresponding width is valid and pass the index check + const shouldDisplay = isResponsive + ? isResponsiveAndFirstRender || + (isIdxCheckPass && getItemWidth(index) > 0) + : isIdxCheckPass; return ( ( item, itemKey: key, registerSize, - display: index <= mergedDisplayCount, + display: shouldDisplay, }} > {renderRawItem(item, index)} @@ -341,6 +355,12 @@ function Overflow( } : (item: ItemType, index: number) => { const key = getKey(item, index); + const isIdxCheckPass = index <= mergedDisplayCount; + + const shouldDisplay = isResponsive + ? isResponsiveAndFirstRender || + (isIdxCheckPass && getItemWidth(index) > 0) + : isIdxCheckPass; return ( ( renderItem={mergedRenderItem} itemKey={key} registerSize={registerSize} - display={index <= mergedDisplayCount} + display={shouldDisplay} /> ); }; diff --git a/tests/__snapshots__/seo.spec.tsx.snap b/tests/__snapshots__/seo.spec.tsx.snap new file mode 100644 index 0000000..160b928 --- /dev/null +++ b/tests/__snapshots__/seo.spec.tsx.snap @@ -0,0 +1,140 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Overflow.SEO invalidate number with suffix 1`] = ` +
+
+ Label 0 +
+
+ Label 1 +
+
+ Label 2 +
+
+ Label 3 +
+
+ + I am a suffix + +
+
+`; + +exports[`Overflow.SEO maxCount number with suffix 1`] = ` +
+
+ Label 0 +
+
+ Label 1 +
+
+ Label 2 +
+
+ Label 3 +
+
+ + 2 ... +
+
+ + I am a suffix + +
+
+`; + +exports[`Overflow.SEO responsive 1`] = ` +
+
+ Label 0 +
+
+ Label 1 +
+ +
+`; + +exports[`Overflow.SEO responsive with suffix 1`] = ` +
+
+ Label 0 +
+
+ Label 1 +
+ +
+ + I am a suffix + +
+
+`; diff --git a/tests/__snapshots__/ssr.spec.tsx.snap b/tests/__snapshots__/ssr.spec.tsx.snap deleted file mode 100644 index 494b349..0000000 --- a/tests/__snapshots__/ssr.spec.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Overflow.SSR basic 1`] = ` -
-
- Label 0 -
-
- Label 1 -
- -
-`; diff --git a/tests/seo.spec.tsx b/tests/seo.spec.tsx new file mode 100644 index 0000000..96f5970 --- /dev/null +++ b/tests/seo.spec.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { render } from 'enzyme'; +import Overflow from '../src'; +import type { OverflowProps } from '../src'; + +interface ItemType { + label: React.ReactNode; + key: React.Key; +} + +interface CaseConf { + name: string; + dataLength: number; + maxCount: OverflowProps['maxCount']; + suffix?: boolean; +} + +function renderItem(item: ItemType) { + return item.label; +} + +describe('Overflow.SEO', () => { + function getData(count: number) { + return new Array(count).fill(undefined).map((_, index) => ({ + label: `Label ${index}`, + key: `k-${index}`, + })); + } + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const testCases: CaseConf[] = [ + { + name: 'responsive', + dataLength: 2, + maxCount: 'responsive', + }, + { + name: 'responsive with suffix', + dataLength: 2, + maxCount: 'responsive', + suffix: true, + }, + { + name: 'maxCount number with suffix', + dataLength: 6, + maxCount: 4, + suffix: true, + }, + { + name: 'invalidate number with suffix', + dataLength: 4, + maxCount: 'invalidate', + suffix: true, + }, + ]; + + testCases.forEach(({ name, dataLength, maxCount: maxCountVal, suffix }) => { + it(`${name}`, () => { + const wrapper = render( + + data={getData(dataLength)} + renderItem={renderItem} + maxCount={maxCountVal} + suffix={suffix && I am a suffix } + />, + ); + + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/ssr.spec.tsx b/tests/ssr.spec.tsx deleted file mode 100644 index 75e5038..0000000 --- a/tests/ssr.spec.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { render } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import Overflow from '../src'; - -interface ItemType { - label: React.ReactNode; - key: React.Key; -} - -function renderItem(item: ItemType) { - return item.label; -} - -describe('Overflow.SSR', () => { - function getData(count: number) { - return new Array(count).fill(undefined).map((_, index) => ({ - label: `Label ${index}`, - key: `k-${index}`, - })); - } - - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('basic', () => { - const wrapper = render( - - data={getData(2)} - renderItem={renderItem} - maxCount="responsive" - ssr="full" - />, - ); - - expect(wrapper).toMatchSnapshot(); - }); -});