diff --git a/.changeset/soft-cards-wave.md b/.changeset/soft-cards-wave.md new file mode 100644 index 0000000..d7ff003 --- /dev/null +++ b/.changeset/soft-cards-wave.md @@ -0,0 +1,11 @@ +--- +'@react-native-motion-kit/swipe-deck': patch +--- + +Lower the minimum `visibleCardCount` to 2 while keeping the default at 3, allowing compact decks to render only the active card and the immediate next card. + +```tsx + item.id} visibleCardCount={2}> + {({ item }) => } + +``` diff --git a/README.ko.md b/README.ko.md index aefd39d..614643e 100644 --- a/README.ko.md +++ b/README.ko.md @@ -103,7 +103,9 @@ Deck은 전체 data set을 한 번에 렌더링하지 않습니다. `visibleCardCount`는 최대 예산입니다. -- `3`보다 작은 값은 데이터가 충분할 때 `3`으로 정규화됩니다. +- 기본값은 더 자연스러운 next-card promotion을 위해 `3`입니다. +- `2`보다 작은 값은 데이터가 충분할 때 `2`로 정규화됩니다. +- `visibleCardCount={2}`는 현재 카드와 바로 다음 카드만 렌더링합니다. - 실제 mount 개수는 active index부터 남은 데이터 개수를 넘지 않습니다. - 짝수 값은 그대로 최대 예산으로 유지되며, 홀수로 올림 처리하지 않습니다. @@ -638,9 +640,17 @@ SwipeDeckMotion.tinder({ ```tsx function CompactDeck() { + return ( + item.id} visibleCardCount={2}> + {/* minimum budget: current + immediate next */} + + ); +} + +function DefaultDeck() { return ( item.id} visibleCardCount={3}> - {/* default/minimum budget */} + {/* default budget for smoother next-card promotion */} ); } @@ -656,7 +666,8 @@ function DeepStackDeck() { | 입력 | Mount되는 card | | ---------------------------------------------- | -------------------------------------------------------- | -| `visibleCardCount={2}` | 데이터가 충분할 때 최대 `3`개 | +| `visibleCardCount={1}` | 데이터가 충분할 때 최대 `2`개 | +| `visibleCardCount={2}` | 데이터가 충분할 때 최대 `2`개 | | 남은 데이터가 10개이고 `visibleCardCount={20}` | 최대 남은 10개 | | 짝수 값 | 최대 예산으로 그대로 유지되며, 홀수로 올림 처리하지 않음 | diff --git a/README.md b/README.md index b279711..cc7774b 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,9 @@ This keeps the active/incoming stack continuous without backfilling dismissed pr `visibleCardCount` is a maximum budget: -- values below `3` normalize to `3` when enough data exists; +- the default is `3` for smoother next-card promotion; +- values below `2` normalize to `2` when enough data exists; +- `visibleCardCount={2}` renders only the current card and the immediate next card; - the mounted count never exceeds the remaining data from the active index; - even values are kept as-is and are not rounded up. @@ -644,9 +646,17 @@ The default is `Easing.out(Easing.cubic)`. ```tsx function CompactDeck() { + return ( + item.id} visibleCardCount={2}> + {/* minimum budget: current + immediate next */} + + ); +} + +function DefaultDeck() { return ( item.id} visibleCardCount={3}> - {/* default/minimum budget */} + {/* default budget for smoother next-card promotion */} ); } @@ -662,7 +672,8 @@ function DeepStackDeck() { | Input | Mounted cards | | ----------------------------------------------- | ------------------------------------------- | -| `visibleCardCount={2}` | Up to `3` cards when enough data exists. | +| `visibleCardCount={1}` | Up to `2` cards when enough data exists. | +| `visibleCardCount={2}` | Up to `2` cards when enough data exists. | | `visibleCardCount={20}` with 10 remaining items | At most those 10 remaining items. | | even values | Kept as the maximum budget; not rounded up. | diff --git a/src/__tests__/rendering.test.ts b/src/__tests__/rendering.test.ts index 1c3fa88..15a3378 100644 --- a/src/__tests__/rendering.test.ts +++ b/src/__tests__/rendering.test.ts @@ -33,6 +33,14 @@ describe('getSwipeRenderItems', () => { expect(getSwipeStackRenderItems(items).map((item) => item.index)).toEqual([77, 76, 75]); }); + it('supports a compact current-plus-next render window', () => { + const items = getSwipeRenderItems(profiles, 75, getProfileKey, 2); + + expect(items).toHaveLength(2); + expect(items.map((item) => item.index)).toEqual([75, 76]); + expect(items.map((item) => item.role)).toEqual(['current', 'next']); + }); + it('does not backfill dismissed previous cards at the last valid index', () => { const items = getSwipeRenderItems(profiles, 149, getProfileKey); @@ -66,6 +74,24 @@ describe('getSwipeRenderItems', () => { ]); }); + it('supports compact undo render info when only current and next are visible', () => { + const items = getSwipeUndoRenderItems({ + data: profiles, + currentIndex: 2, + getKey: getProfileKey, + restoredIndex: 1, + visibleCardCount: 2, + }); + + expect(items.map((item) => item.index)).toEqual([1, 2]); + expect(items.map((item) => item.offset)).toEqual([0, 1]); + expect(items.map((item) => item.role)).toEqual(['current', 'next']); + expect(items.map((item) => item.transition)).toEqual([ + undefined, + { fromOffset: 0, toOffset: 1 }, + ]); + }); + it('skips the restored index from the old stack when data reorder moves it after current', () => { const items = getSwipeUndoRenderItems({ data: profiles, diff --git a/src/__tests__/windowing.test.ts b/src/__tests__/windowing.test.ts index 8b71cc9..e509d27 100644 --- a/src/__tests__/windowing.test.ts +++ b/src/__tests__/windowing.test.ts @@ -47,6 +47,7 @@ describe('getSwipeWindow', () => { }); it('keeps the configured visible card count within the remaining data length', () => { + expect(getSwipeWindow(5, 0, 2)).toHaveLength(2); expect(getSwipeWindow(5, 2, 3)).toHaveLength(3); expect(getSwipeWindow(9, 4, 5)).toHaveLength(5); expect(getSwipeWindow(10, 4, 20)).toHaveLength(6); @@ -72,9 +73,10 @@ describe('getSwipeWindow', () => { }); describe('normalizeVisibleCardCount', () => { - it('defaults and clamps to the three-card minimum', () => { + it('defaults to three cards and clamps to the two-card minimum', () => { expect(normalizeVisibleCardCount()).toBe(3); - expect(normalizeVisibleCardCount(2)).toBe(3); + expect(normalizeVisibleCardCount(1)).toBe(2); + expect(normalizeVisibleCardCount(2)).toBe(2); }); it('keeps larger finite integer budgets', () => { diff --git a/src/core/windowing.ts b/src/core/windowing.ts index 1fba055..e6671de 100644 --- a/src/core/windowing.ts +++ b/src/core/windowing.ts @@ -8,7 +8,7 @@ export type SwipeWindowDescriptor = { }; const DEFAULT_VISIBLE_CARD_COUNT = 3; -const MIN_VISIBLE_CARD_COUNT = 3; +const MIN_VISIBLE_CARD_COUNT = 2; export function clampActiveIndex(dataLength: number, activeIndex: number): number { if (dataLength <= 0) { diff --git a/src/types.ts b/src/types.ts index 5499031..905be6a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -438,12 +438,13 @@ export type SwipeDeckProps = { /** * Maximum number of cards kept mounted from the active card forward. * - * Keep this as small as your UI allows. The default/minimum budget is `3`, which covers the - * active card plus two forward buffered cards for smooth next-card promotion without rendering - * the full data set. Increase it only when your design visibly exposes a deeper stack or - * intentionally needs more cards pre-mounted. + * Keep this as small as your UI allows. The default budget is `3`, which covers the active card + * plus two forward buffered cards for smooth next-card promotion without rendering the full data + * set. The minimum budget is `2`, which renders only the active card and the immediate next card + * when enough forward data is available. Increase it only when your design visibly exposes a + * deeper stack or intentionally needs more cards pre-mounted. * - * Values below `3` are normalized to `3` when enough forward data is available. The actual + * Values below `2` are normalized to `2` when enough forward data is available. The actual * mounted count never exceeds the remaining item count from the active index. * * @default 3