refactor(card-stack): bare-bones API#506
Conversation
…g tiers Strip Card Stack to stack mechanics only. Profile UI moves to a demo module. Document the four-tier changelog process and sync entries for the API change. Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughWalkthroughThis PR refactors CardStack primitives to be fully generic over item shape, extracts profile-specific UI into reusable components, refactors the trigger interaction with a new "full" overlay mode, and introduces a new four-tier release-notes documentation framework. ChangesCardStack Core Refactor & Profile Extraction
Four-Tier Changelog Documentation Framework
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
animata/card/card-stack.tsx (1)
287-293:⚠️ Potential issue | 🟠 MajorKeep
onItemsChangeout of thesetItemListupdater.
rotateOnecallsonItemsChangeinsidesetItemList((current) => { ... }), making the updater impure; React may invoke updater functions more than once (e.g., in Strict Mode), causing the callback to fire multiple times. Return the new list from the updater, and invokeonItemsChangeafter commit (e.g., via an effect/ref based on the committeditemList).
- Forward
onKeyDownto the non-full trigger<button>.
CardStackTriggercomputeshandlers.onKeyDown, but thefull=falsebranch’s<button>doesn’t passonKeyDown={handlers.onKeyDown}, so the consumer-providedonKeyDownis ignored there.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@animata/card/card-stack.tsx` around lines 287 - 293, rotateOne currently calls onItemsChange from inside the setItemList updater (making the updater impure) — change rotateOne to only return the new list from setItemList (e.g., setItemList(current => { if (current.length<=1) return current; const next=[...current]; next.push(next.shift()!); return next; })), and then invoke onItemsChange after state has committed by using a separate effect or a ref-based post-update notifier that watches the committed itemList (reference rotateOne and setItemList to locate the code and add a useEffect that calls onItemsChange(itemList) or a guarded ref to avoid duplicate calls). Also update CardStackTrigger so the non-full branch's <button> forwards the computed keyboard handler: pass onKeyDown={handlers.onKeyDown} to the button rendered when full === false (locate CardStackTrigger and the branch rendering the non-full <button>).
🧹 Nitpick comments (1)
app/demo/library/hero/photographer-portfolio.tsx (1)
78-82: ⚡ Quick winNarrow
PortfolioItem.idtoPortfolioId.After the core item was generalized, this local type widened
idback to plainstring, which is whyPrintCaptionnow needsas PortfolioIdand a fallback branch. TypingidasPortfolioIdkeepsPORTFOLIOandSHOT_SETTINGSin lockstep and removes the unsafe cast downstream.Suggested shape
-type PortfolioItem = CardStackItem & { +type PortfolioItem = CardStackItem & { + id: PortfolioId; image: string; title: string; tagline: string; };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/demo/library/hero/photographer-portfolio.tsx` around lines 78 - 82, The local type PortfolioItem currently widens id to string; change its id to the existing PortfolioId type so the shape stays in sync with PORTFOLIO and SHOT_SETTINGS and removes the unsafe cast/fallback in PrintCaption—update the PortfolioItem declaration to use id: PortfolioId and then remove the as PortfolioId cast and fallback logic in usages like PrintCaption.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@animata/card/card-stack.tsx`:
- Around line 524-533: The non-full branch of the CardStack trigger fails to
forward the onKeyDown prop, breaking consumer keyboard handlers; update the
Button render in card-stack.tsx to accept and pass the onKeyDown handler (from
CardStackTriggerProps) to the <button> alongside the existing handlers (e.g.,
handlers.onPointerDown, handlers.onClick) so the button receives onKeyDown and
consumer-provided keyboard behavior is preserved.
In `@app/`(main)/_landing/card-stack-bento.tsx:
- Around line 39-42: The viewed counter is incremented directly in
CardStack.Trigger's onClick which can overcount during the stack's animation;
instead derive/update viewed only when the stack actually advances (e.g., using
CardStack's onItemsChange or activeItem) so it reflects committed rotations.
Replace the onClick setViewed usage with logic that listens to the CardStack's
onItemsChange/activeItem update and sets viewed = Math.min(BENTO_PEOPLE.length,
newActiveIndex + 1) (or increment only inside the successful-advance callback),
referencing CardStack.Trigger, setViewed, viewed, BENTO_PEOPLE, activeItem and
onItemsChange to locate and fix the code.
In `@app/demo/library/shared/profile-stack-card.tsx`:
- Around line 24-31: The trailing arrow color uses a hardcoded class
("text-black/40") which fails in dark mode; update the footerTrailing prop on
CardStackProfileCard to use the theme token (e.g., "text-muted-foreground")
instead of "text-black/40" so the ArrowRight icon remains readable across
light/dark themes—modify the ArrowRight className passed into footerTrailing
accordingly.
In `@content/docs/changelog/index.mdx`:
- Line 15: Update the changelog entries to use explicit past-tense action
phrasing: replace "Wave 1 widget glance polish" with something like "Polished
Wave 1 widget glance interactions", change "Card Spread throw motion" to "Added
throw motion to Card Spread", change "Card Stack API trim" to "Trimmed the Card
Stack API", and change "catalog cleanup for published widgets only" to "Cleaned
up catalog to include only published widgets"; apply these edits in
content/docs/changelog/index.mdx where those phrases appear.
---
Outside diff comments:
In `@animata/card/card-stack.tsx`:
- Around line 287-293: rotateOne currently calls onItemsChange from inside the
setItemList updater (making the updater impure) — change rotateOne to only
return the new list from setItemList (e.g., setItemList(current => { if
(current.length<=1) return current; const next=[...current];
next.push(next.shift()!); return next; })), and then invoke onItemsChange after
state has committed by using a separate effect or a ref-based post-update
notifier that watches the committed itemList (reference rotateOne and
setItemList to locate the code and add a useEffect that calls
onItemsChange(itemList) or a guarded ref to avoid duplicate calls). Also update
CardStackTrigger so the non-full branch's <button> forwards the computed
keyboard handler: pass onKeyDown={handlers.onKeyDown} to the button rendered
when full === false (locate CardStackTrigger and the branch rendering the
non-full <button>).
---
Nitpick comments:
In `@app/demo/library/hero/photographer-portfolio.tsx`:
- Around line 78-82: The local type PortfolioItem currently widens id to string;
change its id to the existing PortfolioId type so the shape stays in sync with
PORTFOLIO and SHOT_SETTINGS and removes the unsafe cast/fallback in
PrintCaption—update the PortfolioItem declaration to use id: PortfolioId and
then remove the as PortfolioId cast and fallback logic in usages like
PrintCaption.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8d0f25d1-c0cb-4a1b-a029-2c8b06ed19b0
⛔ Files ignored due to path filters (1)
app/demo/generated/demo-sources.tsis excluded by!**/generated/**
📒 Files selected for processing (17)
CLAUDE.mdanimata/card/card-stack-profile.tsxanimata/card/card-stack.stories.tsxanimata/card/card-stack.tsxapp/(main)/_landing/card-stack-bento.tsxapp/demo/library/hero/photographer-portfolio.tsxapp/demo/library/shared/card-stack-people.tsapp/demo/library/shared/profile-stack-card.tsxconfig/docs.tscontent/docs/card/card-stack.mdxcontent/docs/card/index.mdxcontent/docs/changelog/2026-06.mdxcontent/docs/changelog/index.mdxcontent/docs/contributing/changelog.mdxcontent/docs/contributing/components.mdxcontent/docs/contributing/index.mdxscripts/validate-registry-install.js
Move onItemsChange out of the setState updater, wire Trigger onKeyDown on buttons, derive bento viewed count from rotation, tighten PortfolioItem typing, and past-tense the June changelog summary. Co-authored-by: Cursor <cursoragent@cursor.com>
Deploying animata with
|
| Latest commit: |
f655947
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://21b241ce.animata.pages.dev |
| Branch Preview URL: | https://feat-card-stack-refactor.animata.pages.dev |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
animata/card/card-stack.tsx (1)
521-547:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse a real
<button>for thefulloverlay path.The
fullbranch reimplements button behavior on adiv, so Enter/Space advances the stack without ever hitting the consumeronClickcallback. Pointer activation and the non-fullbranch do callonClick, so the trigger API behaves differently for keyboard users infullmode.Proposed fix
if (full) { return ( - // biome-ignore lint/a11y/useSemanticElements: overlay sits above card content; a wrapping <button> would be invalid markup - <div - role="button" - tabIndex={0} + <button + type="button" aria-label={ariaLabel} - {...handlers} + onPointerDown={handlers.onPointerDown} + onPointerUp={handlers.onPointerUp} + onPointerLeave={handlers.onPointerLeave} + onPointerCancel={handlers.onPointerCancel} + onClick={handlers.onClick} + onKeyDown={onKeyDown} className={cn( "absolute inset-0 z-40 cursor-pointer outline-none", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", className, )} /> ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@animata/card/card-stack.tsx` around lines 521 - 547, The full branch renders a div that imitates button behavior, which breaks keyboard activation (Enter/Space) and bypasses the consumer onClick; change the full branch to render a real <button> element instead of the div, remove role="button" and tabIndex, keep aria-label={ariaLabel}, spread {...handlers} or explicitly attach handlers.onClick and onKeyDown like the non-full branch so keyboard and pointer activation both call handlers.onClick and onKeyDown, and preserve className and focus-visible classes (keep the biome-ignore lint comment if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@animata/card/card-stack.tsx`:
- Around line 521-547: The full branch renders a div that imitates button
behavior, which breaks keyboard activation (Enter/Space) and bypasses the
consumer onClick; change the full branch to render a real <button> element
instead of the div, remove role="button" and tabIndex, keep
aria-label={ariaLabel}, spread {...handlers} or explicitly attach
handlers.onClick and onKeyDown like the non-full branch so keyboard and pointer
activation both call handlers.onClick and onKeyDown, and preserve className and
focus-visible classes (keep the biome-ignore lint comment if needed).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6af53971-63fd-47cc-aafc-920bbc82fb59
⛔ Files ignored due to path filters (1)
app/demo/generated/demo-sources.tsis excluded by!**/generated/**
📒 Files selected for processing (5)
animata/card/card-stack.tsxapp/(main)/_landing/card-stack-bento.tsxapp/demo/library/hero/photographer-portfolio.tsxapp/demo/library/shared/profile-stack-card.tsxcontent/docs/changelog/index.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
- content/docs/changelog/index.mdx
- app/demo/library/hero/photographer-portfolio.tsx
Trigger full prop, and changelog tiers
Strip Card Stack to stack mechanics only. Profile UI moves to a demo module. Document the four-tier changelog process and sync entries for the API change.
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Documentation