/**
* StackCard — a pile of cards, taken one at a time by throwing the top one off.
*
* ```tsx
* decide(people[index], direction)}>
* Yes
* No
* {people.map((person) => (
*
* {person.name}
*
* ))}
*
* Nobody left
*
*
* ```
*
* For a queue of things each answered with one decision and then gone: a
* review queue, a set of flashcards, an inbox of suggestions. The gesture is
* the answer, which is what makes it quicker than a list of rows with buttons
* on them — and what makes it wrong for anything the reader has to compare,
* skim or come back to. A deck shows one card and hides the rest.
*
* For a run of slides the reader browses rather than disposes of, use
* [Carousel](../carousel); for one row's actions in a list, [Swipe](../swipe).
*
* ## The pile is one drag, read by everything
*
* The top card's `x` and `y` are the only values a gesture writes, and every
* other moving part is derived from them: each stamp fades in on how far the
* drag has carried the card toward its own direction, and the cards behind
* climb toward the top position on the furthest of those.
*
* That derivation is also what makes a dismissal seamless. By the time the top
* card has been carried far enough to leave, the second card is already
* exactly where the top card sits — so when the deck advances there is nothing
* left for it to move, and no frame in which the pile re-arranges itself.
*
* ## The deck moves on the UI thread, and React catches up
*
* Which card is on top is held twice: as React state, for what is mounted and
* for the callbacks, and as a shared value, for what is drawn. A throw is
* handled entirely on the UI thread — the card leaves on the frame the finger
* lets go, and once it is off the screen the top card, the offset and the fade
* all move in one step — and only then is the new index requested from React.
*
* Nothing on the screen waits for a render, so a busy JavaScript thread cannot
* stall a throw or leave a frame where the two halves disagree. What React
* renders afterwards is kept from showing before the UI thread agrees with it:
* a stamp is only drawn on the card the UI thread has on top.
*
* A controlled deck is still the owner's to decide. An owner that declines the
* new index gets the card back, flown in from the way it went; one that
* accepts it sees nothing move at all, because it already has.
*
* One card behind the pile's stated depth stays mounted so it can fade in as
* it takes the last visible place, and one card ahead of the top stays mounted
* so `undo` has something to fly back in. The rest are unmounted, which is
* what makes a deck of five hundred cost what a deck of five costs.
*
* ## A deck is not reachable by a gesture alone
*
* A throw is not available to a screen reader, and neither is a card that can
* only be answered by throwing it. So the top card publishes an accessibility
* action for every direction the deck accepts, and `StackCard.Action` renders
* the same decisions as ordinary buttons — which sighted people reach for too,
* on the card they are not sure about.
*/
import { type ReactNode } from 'react';
import { View, type ViewProps } from 'react-native';
import { type SharedValue } from 'react-native-reanimated';
import { type VariantProps } from 'tailwind-variants';
import { type StackCardDirection } from './stack-card-geometry.js';
export type { StackCardDirection } from './stack-card-geometry.js';
/**
* The deck's state, for a control that lives outside the pile — a counter, a
* progress bar, a button in a toolbar.
*
* `release` is a shared value running 0 to 1 as the top card is carried toward
* leaving, so something beside the deck can move with the drag rather than
* starting a second animation next to it.
*/
export declare function useStackCard(): {
/** How many cards have been answered, which is also the top card's index. */
index: number;
/** How many cards the deck was given. */
count: number;
/** How many are left, the top one included. */
remaining: number;
/** Whether there is a card to bring back. */
canUndo: boolean;
release: SharedValue;
send: (direction: StackCardDirection) => void;
undo: () => void;
reset: () => void;
};
/** How the cards behind the top one are arranged. */
export type StackCardLayout = 'stack' | 'fan' | 'flat';
export interface StackCardHandle {
/** Send the top card away as though it had been thrown that way. */
swipe: (direction: StackCardDirection) => void;
/** Bring the last card back, and with it the decision that removed it. */
undo: () => void;
/** Put every card back. */
reset: () => void;
}
export interface StackCardProps extends Omit {
/**
* A `StackCard.Card` for each card, plus any of `StackCard.Stamp`,
* `StackCard.Empty` and `StackCard.Actions`, in any order. Anything else is
* laid out under the pile.
*/
children?: ReactNode;
/**
* Which card is on top, when the caller holds it. Leave unset to let the
* deck keep its own. A controlled deck that declines a request stays where
* it is and the thrown card comes back, so this is also how a decision is
* confirmed before it is taken.
*/
index?: number;
/** Which card an uncontrolled deck starts on. */
defaultIndex?: number;
/** Fires whenever the deck asks to move, with the index it is asking for. */
onIndexChange?: (index: number) => void;
/**
* Fires when a card leaves, with the way it went and the index it was at.
* It fires before `onIndexChange` asks for the next index. Not called by
* `undo` — the index going back is what reports that.
*/
onSwipe?: (direction: StackCardDirection, index: number) => void;
/** Fires once when the last card leaves. */
onEmpty?: () => void;
/**
* Which ways a card may be thrown. Left and right by default.
*
* A direction left out still follows the finger a little and then comes
* back, rather than refusing to move at all — a card that does not budge
* reads as a frozen screen.
*/
directions?: readonly StackCardDirection[];
/**
* How the cards behind the top one are arranged. `stack` steps them down and
* back; `fan` turns them alternately, like a hand of cards; `flat` hides them
* entirely, for full-bleed cards where a peeking edge is only clutter.
*/
layout?: StackCardLayout;
/** How many cards are drawn behind the top one. Two is a pile; five is a mess. */
depth?: number;
/**
* How far a card has to be taken for a release to send it away, as a
* fraction of the card. Momentum counts toward it, so a flick clears it
* without travelling.
*/
threshold?: number;
/** Stop the deck taking a gesture, without changing how it looks. */
disabled?: boolean;
/** A tick when a drag first reaches the point of no return, and a knock as the card goes. */
haptics?: boolean;
/**
* What a screen reader is offered for each direction, in place of "Swipe
* left". Name the decision — `{ left: 'Skip', right: 'Save' }`.
*/
directionLabels?: Partial>;
/** Classes for the whole control. Give it a height; the pile fills what is left. */
className?: string;
/** Classes for the box the cards are laid out in. */
pileClassName?: string;
}
export interface StackCardCardProps extends ViewProps {
className?: string;
children?: ReactNode;
}
/**
* A stamp is a filled block of colour with a word on it, turned a few degrees
* so it reads as pressed onto the card rather than laid out on it.
*
* The fill is the status colour at full strength rather than a tint of it: a
* stamp exists only for the moment it appears, and a six-per-cent wash of the
* card's own surface is a smudge rather than an answer. The word is carried in
* white, which is what the status colours are chosen to take — a status's
* `-foreground` token is its darker text form, meant for a neutral surface,
* and over the fill it is the same hue twice.
*
* It sits in the corner the card is being pulled away from, which is also the
* corner the thumb is not over.
*/
declare const stampVariants: import("tailwind-variants").TVReturnType<{
color: {
default: {
pill: string;
label: string;
};
primary: {
pill: string;
label: string;
};
success: {
pill: string;
label: string;
};
warning: {
pill: string;
label: string;
};
info: {
pill: string;
label: string;
};
destructive: {
pill: string;
label: string;
};
};
direction: {
left: {
root: string;
pill: string;
};
right: {
root: string;
pill: string;
};
up: {
root: string;
};
down: {
root: string;
};
};
}, {
root: string;
pill: string;
label: string;
}, undefined, {
color: {
default: {
pill: string;
label: string;
};
primary: {
pill: string;
label: string;
};
success: {
pill: string;
label: string;
};
warning: {
pill: string;
label: string;
};
info: {
pill: string;
label: string;
};
destructive: {
pill: string;
label: string;
};
};
direction: {
left: {
root: string;
pill: string;
};
right: {
root: string;
pill: string;
};
up: {
root: string;
};
down: {
root: string;
};
};
}, {
root: string;
pill: string;
label: string;
}, import("tailwind-variants").TVReturnType<{
color: {
default: {
pill: string;
label: string;
};
primary: {
pill: string;
label: string;
};
success: {
pill: string;
label: string;
};
warning: {
pill: string;
label: string;
};
info: {
pill: string;
label: string;
};
destructive: {
pill: string;
label: string;
};
};
direction: {
left: {
root: string;
pill: string;
};
right: {
root: string;
pill: string;
};
up: {
root: string;
};
down: {
root: string;
};
};
}, {
root: string;
pill: string;
label: string;
}, undefined, unknown, unknown, undefined>>;
export type StackCardStampColor = 'default' | 'primary' | 'success' | 'warning' | 'info' | 'destructive';
export interface StackCardStampProps extends Omit, VariantProps {
className?: string;
/** The word, or anything else to draw on the stamp. */
children?: ReactNode;
/** Which direction the stamp answers for. Also where on the card it goes. */
direction?: StackCardDirection;
/** Extra classes for the label, when the stamp is given a string. */
labelClassName?: string;
}
export interface StackCardEmptyProps extends ViewProps {
className?: string;
children?: ReactNode;
}
export interface StackCardActionsProps extends ViewProps {
className?: string;
children?: ReactNode;
}
declare const actionVariants: import("tailwind-variants").TVReturnType<{
color: {
default: {
root: string;
};
primary: {
root: string;
};
success: {
root: string;
};
warning: {
root: string;
};
info: {
root: string;
};
destructive: {
root: string;
};
};
size: {
sm: {
root: string;
};
md: {
root: string;
};
lg: {
root: string;
};
};
}, {
root: string;
}, undefined, {
color: {
default: {
root: string;
};
primary: {
root: string;
};
success: {
root: string;
};
warning: {
root: string;
};
info: {
root: string;
};
destructive: {
root: string;
};
};
size: {
sm: {
root: string;
};
md: {
root: string;
};
lg: {
root: string;
};
};
}, {
root: string;
}, import("tailwind-variants").TVReturnType<{
color: {
default: {
root: string;
};
primary: {
root: string;
};
success: {
root: string;
};
warning: {
root: string;
};
info: {
root: string;
};
destructive: {
root: string;
};
};
size: {
sm: {
root: string;
};
md: {
root: string;
};
lg: {
root: string;
};
};
}, {
root: string;
}, undefined, unknown, unknown, undefined>>;
export interface StackCardActionProps extends Omit, VariantProps {
className?: string;
/** What pressing it does: send the top card that way, or bring the last one back. */
action: StackCardDirection | 'undo';
/** The glyph. Sized and tinted by the button — pass neither. */
icon?: ReactNode;
/**
* What a screen reader is offered. Falls back to "Undo", or to the plain
* name of the direction.
*/
label?: string;
/** Run after the deck has been told, for a sound or a log. */
onPress?: () => void;
}
export declare const StackCard: import("react").ForwardRefExoticComponent> & {
Card: import("react").ForwardRefExoticComponent>;
Stamp: import("react").ForwardRefExoticComponent>;
Empty: import("react").ForwardRefExoticComponent>;
Actions: import("react").ForwardRefExoticComponent>;
Action: import("react").ForwardRefExoticComponent>;
};
//# sourceMappingURL=index.d.ts.map