CardOpening
CardOpening is the reveal itself. It renders nothing until a card is opened, then takes the screen: the card leaves an element you name, flies to the middle face down, hums, turns over with the show its tier asked for, and shrinks into the element where it belongs.
Each card leaves its own place and lands back on it.
Usage
import { useCardOpening, useOpenedCards } from "@react-cards-opening/core";
import { CardOpening, themeOf } from "@react-cards-opening/ui";
function Shop({ cards }) {
const { opened, open } = useOpenedCards("shop");
const hunt = useCardOpening({ cards, opened, onOpen: open });
const places = useRef(new Map<string, HTMLElement>());
return (
<>
{cards.map((card) => (
<button
key={card.id}
ref={(element) => element && places.current.set(card.id, element)}
onClick={() => hunt.start([card.id])}
>
Open
</button>
))}
<CardOpening
opening={hunt.opening}
theme={themeOf("grape", "dark")}
launch={(card) => places.current.get(card.id) ?? null}
landing={(card) => places.current.get(card.id) ?? null}
/>
</>
);
}Props
| Prop | Type | What it does |
|---|---|---|
opening | Opening | The rules, from useCardOpening. |
theme? | CardTheme | The colours. moss in the dark by default. |
rarities? | readonly RarityTier[] | Your tiers. The six defaults otherwise. |
launch? | (card, show) => HTMLElement | CardLaunch | null | Where the card comes out of. Asked once per card. With nothing, the card grows out of the middle of the screen. |
landing? | (card) => HTMLElement | null | Where the card goes. Asked for a few frames running, since the element may not be rendered yet. With nowhere, the card fades out. |
renderFace? | (card, info) => ReactNode | The front. Rows with the class rco-line rise in as it turns. |
renderBack? | (card) => ReactNode | The back, instead of the question glyph. |
noteOf? | (card) => string | undefined | A small last line on the ready-made face. |
readingOf? | (card, info) => readonly string[] | The text on a face of your own, so the card stays up long enough to read. |
labels? | Partial<CardOpeningLabels> | What a screen reader and a tooltip say. |
effects? | EffectPlan | false | What each tier plays on top of the card's own reveal. The signature shows when left out, false for none. |
classNames? | CardOpeningClassNames | A class of your own for each part. See Styling by hand. |
width? | number | The card's width in pixels. 300 by default, 78% of a narrow screen at most. |
ratio? | number | Height over width. 4 / 3 by default. |
container? | Element | null | Where the overlay mounts. The document's body by default. |
Something to play first
launch can return more than an element. Hand back a GSAP timeline as lead and the card waits for it: a lid lifting, a pack tearing, a scan closing in. release is called when the card has landed or the run was stopped, to put things back.
<CardOpening
opening={hunt.opening}
launch={(card, { tone }) => {
const box = boxRef.current;
if (!box) return null;
const lead = gsap.timeline();
lead.to(box, { rotate: -6, duration: 0.08, yoyo: true, repeat: 5 });
lead.to(box, { boxShadow: `0 0 40px 6px ${tone}`, duration: 0.3 });
return { element: box, lead, release: () => gsap.set(box, { clearProps: "all" }) };
}}
/>The second argument says what is about to happen: tone is the tier's colour on the theme, plan is its show plan, and reduced is whether motion is reduced.
Presses
The card is a button and the dimmed page behind it is the backdrop. What each press does is decided by the rules: face down, a press turns the card; face up, it sends it home; a press beside it stops the run.
Looking at a card again
<button onClick={(event) => hunt.peek(card.id, event.currentTarget)}>{card.name}</button>A peek brings an opened card up out of what was pressed, face up, with no turn. It stays until a press on it, a press beside it or Escape sends it back, and focus returns to where it came from.
- Can two openings be on one page?
- Yes. Each
useCardOpeninghas its own rules and eachCardOpeningits own overlay. Nothing is kept onwindow. - Why does the card not fly to my landing element?
landingreturned null for thirty frames running, or motion is reduced. Check that the element is rendered by the time the card turns over: it is asked for afteronOpenfires.- Does it lock scrolling?
- No. The backdrop takes presses while a card is up, and positions are measured when the card leaves and again when it lands, so a page that scrolled in between is still right.