From 2e332dae492edd3f1a9d92cd973ca08bd0a995e8 Mon Sep 17 00:00:00 2001 From: harang Date: Wed, 17 Jun 2026 23:35:05 +0900 Subject: [PATCH] feat: allow current-only visible card budget Lower the visibleCardCount floor to one so consumers can opt into the lightest current-card-only rendering mode. The default remains three cards, so existing smoother next-card promotion behavior stays unchanged unless consumers explicitly pass visibleCardCount={1}. Constraint: Keep default visibleCardCount at 3 Constraint: Avoid gesture or motion behavior changes Rejected: Change the default to 1 | would remove next-card promotion for existing users Confidence: high Scope-risk: narrow Tested: yarn typecheck; yarn lint; yarn format:check Tested: yarn jest --runInBand --watchman=false --- .changeset/visible-card-count-one.md | 15 +++++++++++++++ README.ko.md | 11 ++++++----- README.md | 11 ++++++----- src/__tests__/rendering.test.ts | 24 ++++++++++++++++++++++++ src/__tests__/windowing.test.ts | 6 ++++-- src/core/windowing.ts | 2 +- src/types.ts | 8 ++++---- 7 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 .changeset/visible-card-count-one.md diff --git a/.changeset/visible-card-count-one.md b/.changeset/visible-card-count-one.md new file mode 100644 index 0000000..710b977 --- /dev/null +++ b/.changeset/visible-card-count-one.md @@ -0,0 +1,15 @@ +--- +'@react-native-motion-kit/swipe-deck': minor +--- + +Allow `visibleCardCount={1}` to render only the active card. + +This is the lightest rendering budget for decks that do not need a visible next-card stack or +next-card promotion animation. The default remains `3`, and `visibleCardCount={2}` still renders the +active card plus the immediate next card. + +```tsx + item.id} visibleCardCount={1}> + {({ item }) => } + +``` diff --git a/README.ko.md b/README.ko.md index 5a8ad8d..f077c98 100644 --- a/README.ko.md +++ b/README.ko.md @@ -104,7 +104,8 @@ Deck은 전체 data set을 한 번에 렌더링하지 않습니다. `visibleCardCount`는 최대 예산입니다. - 기본값은 더 자연스러운 next-card promotion을 위해 `3`입니다. -- `2`보다 작은 값은 데이터가 충분할 때 `2`로 정규화됩니다. +- `1`보다 작은 값은 데이터가 충분할 때 `1`로 정규화됩니다. +- `visibleCardCount={1}`는 현재 카드만 렌더링하므로 next-card stack/promotion 시각 효과는 보이지 않습니다. - `visibleCardCount={2}`는 현재 카드와 바로 다음 카드만 렌더링합니다. - 실제 mount 개수는 active index부터 남은 데이터 개수를 넘지 않습니다. - 짝수 값은 그대로 최대 예산으로 유지되며, 홀수로 올림 처리하지 않습니다. @@ -666,10 +667,10 @@ SwipeDeckMotion.tinder({ ## Visible card budget ```tsx -function CompactDeck() { +function CurrentOnlyDeck() { return ( - item.id} visibleCardCount={2}> - {/* minimum budget: current + immediate next */} + item.id} visibleCardCount={1}> + {/* 가장 가벼운 예산: 현재 카드만 렌더링, next-card promotion 시각 효과 없음 */} ); } @@ -693,7 +694,7 @@ function DeepStackDeck() { | 입력 | Mount되는 card | | ---------------------------------------------- | -------------------------------------------------------- | -| `visibleCardCount={1}` | 데이터가 충분할 때 최대 `2`개 | +| `visibleCardCount={1}` | 현재 카드만 | | `visibleCardCount={2}` | 데이터가 충분할 때 최대 `2`개 | | 남은 데이터가 10개이고 `visibleCardCount={20}` | 최대 남은 10개 | | 짝수 값 | 최대 예산으로 그대로 유지되며, 홀수로 올림 처리하지 않음 | diff --git a/README.md b/README.md index 6e83b4c..bd0b30e 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,8 @@ This keeps the active/incoming stack continuous without backfilling dismissed pr `visibleCardCount` is a maximum budget: - the default is `3` for smoother next-card promotion; -- values below `2` normalize to `2` when enough data exists; +- values below `1` normalize to `1` when enough data exists; +- `visibleCardCount={1}` renders only the current card, so no next-card stack/promotion visual is shown; - `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. @@ -673,10 +674,10 @@ The default is `Easing.out(Easing.cubic)`. ## Visible card budget ```tsx -function CompactDeck() { +function CurrentOnlyDeck() { return ( - item.id} visibleCardCount={2}> - {/* minimum budget: current + immediate next */} + item.id} visibleCardCount={1}> + {/* lightest budget: current card only, no next-card promotion visual */} ); } @@ -700,7 +701,7 @@ function DeepStackDeck() { | Input | Mounted cards | | ----------------------------------------------- | ------------------------------------------- | -| `visibleCardCount={1}` | Up to `2` cards when enough data exists. | +| `visibleCardCount={1}` | Only the current card. | | `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 15a3378..6edff46 100644 --- a/src/__tests__/rendering.test.ts +++ b/src/__tests__/rendering.test.ts @@ -41,6 +41,15 @@ describe('getSwipeRenderItems', () => { expect(items.map((item) => item.role)).toEqual(['current', 'next']); }); + it('supports a current-only render window', () => { + const items = getSwipeRenderItems(profiles, 75, getProfileKey, 1); + + expect(items).toHaveLength(1); + expect(items.map((item) => item.index)).toEqual([75]); + expect(items.map((item) => item.role)).toEqual(['current']); + expect(items.map((item) => item.isActive)).toEqual([true]); + }); + it('does not backfill dismissed previous cards at the last valid index', () => { const items = getSwipeRenderItems(profiles, 149, getProfileKey); @@ -92,6 +101,21 @@ describe('getSwipeRenderItems', () => { ]); }); + it('supports current-only undo render info', () => { + const items = getSwipeUndoRenderItems({ + data: profiles, + currentIndex: 2, + getKey: getProfileKey, + restoredIndex: 1, + visibleCardCount: 1, + }); + + expect(items.map((item) => item.index)).toEqual([1]); + expect(items.map((item) => item.offset)).toEqual([0]); + expect(items.map((item) => item.role)).toEqual(['current']); + expect(items.map((item) => item.transition)).toEqual([undefined]); + }); + 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 2d1e040..48068b8 100644 --- a/src/__tests__/windowing.test.ts +++ b/src/__tests__/windowing.test.ts @@ -52,6 +52,7 @@ describe('getSwipeWindow', () => { }); it('keeps the configured visible card count within the remaining data length', () => { + expect(getSwipeWindow(5, 0, 1)).toHaveLength(1); expect(getSwipeWindow(5, 0, 2)).toHaveLength(2); expect(getSwipeWindow(5, 2, 3)).toHaveLength(3); expect(getSwipeWindow(9, 4, 5)).toHaveLength(5); @@ -78,9 +79,10 @@ describe('getSwipeWindow', () => { }); describe('normalizeVisibleCardCount', () => { - it('defaults to three cards and clamps to the two-card minimum', () => { + it('defaults to three cards and clamps to the one-card minimum', () => { expect(normalizeVisibleCardCount()).toBe(3); - expect(normalizeVisibleCardCount(1)).toBe(2); + expect(normalizeVisibleCardCount(0)).toBe(1); + expect(normalizeVisibleCardCount(1)).toBe(1); expect(normalizeVisibleCardCount(2)).toBe(2); }); diff --git a/src/core/windowing.ts b/src/core/windowing.ts index e6671de..a1aa28f 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 = 2; +const MIN_VISIBLE_CARD_COUNT = 1; export function clampActiveIndex(dataLength: number, activeIndex: number): number { if (dataLength <= 0) { diff --git a/src/types.ts b/src/types.ts index afea3c2..aa9136f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -448,11 +448,11 @@ export type SwipeDeckProps = { * * 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. + * set. The minimum budget is `1`, which renders only the active card. Use `2` when your design + * needs the immediate next card pre-mounted for stack/promotion visuals, and increase it only when + * your design visibly exposes a deeper stack or intentionally needs more cards pre-mounted. * - * Values below `2` are normalized to `2` when enough forward data is available. The actual + * Values below `1` are normalized to `1` when enough forward data is available. The actual * mounted count never exceeds the remaining item count from the active index. * * @default 3