Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/visible-card-count-one.md
Original file line number Diff line number Diff line change
@@ -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
<ProfileDeck.Root data={profiles} getKey={(item) => item.id} visibleCardCount={1}>
<ProfileDeck.Card>{({ item }) => <ProfileCard profile={item} />}</ProfileDeck.Card>
</ProfileDeck.Root>
```
11 changes: 6 additions & 5 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -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부터 남은 데이터 개수를 넘지 않습니다.
- 짝수 값은 그대로 최대 예산으로 유지되며, 홀수로 올림 처리하지 않습니다.
Expand Down Expand Up @@ -666,10 +667,10 @@ SwipeDeckMotion.tinder({
## Visible card budget

```tsx
function CompactDeck() {
function CurrentOnlyDeck() {
return (
<SwipeDeck.Root data={profiles} getKey={(item) => item.id} visibleCardCount={2}>
{/* minimum budget: current + immediate next */}
<SwipeDeck.Root data={profiles} getKey={(item) => item.id} visibleCardCount={1}>
{/* 가장 가벼운 예산: 현재 카드만 렌더링, next-card promotion 시각 효과 없음 */}
</SwipeDeck.Root>
);
}
Expand All @@ -693,7 +694,7 @@ function DeepStackDeck() {

| 입력 | Mount되는 card |
| ---------------------------------------------- | -------------------------------------------------------- |
| `visibleCardCount={1}` | 데이터가 충분할 때 최대 `2`개 |
| `visibleCardCount={1}` | 현재 카드만 |
| `visibleCardCount={2}` | 데이터가 충분할 때 최대 `2`개 |
| 남은 데이터가 10개이고 `visibleCardCount={20}` | 최대 남은 10개 |
| 짝수 값 | 최대 예산으로 그대로 유지되며, 홀수로 올림 처리하지 않음 |
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -673,10 +674,10 @@ The default is `Easing.out(Easing.cubic)`.
## Visible card budget

```tsx
function CompactDeck() {
function CurrentOnlyDeck() {
return (
<SwipeDeck.Root data={profiles} getKey={(item) => item.id} visibleCardCount={2}>
{/* minimum budget: current + immediate next */}
<SwipeDeck.Root data={profiles} getKey={(item) => item.id} visibleCardCount={1}>
{/* lightest budget: current card only, no next-card promotion visual */}
</SwipeDeck.Root>
);
}
Expand All @@ -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. |
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions src/__tests__/windowing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
});

Expand Down
2 changes: 1 addition & 1 deletion src/core/windowing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,11 +448,11 @@ export type SwipeDeckProps<T> = {
*
* 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
Expand Down