Skip to content

refactor(card-stack): bare-bones API#506

Merged
hari merged 2 commits into
mainfrom
feat/card-stack-refactor
Jun 12, 2026
Merged

refactor(card-stack): bare-bones API#506
hari merged 2 commits into
mainfrom
feat/card-stack-refactor

Conversation

@sudhashrestha

@sudhashrestha sudhashrestha commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

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

    • Click-anywhere card advancement via a new full-mode Trigger
    • Dedicated profile-style card components and demo wrappers
  • Refactor

    • Card Stack API trimmed and generalized for custom item types
    • Landing and demo usages updated to use the profile stack composition
  • Bug Fixes

    • Fixed invalid HTML from wrapping flow content in a button
  • Documentation

    • Updated Card Stack docs and site changelogs; introduced a four-tier release-notes process

…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>
@sudhashrestha sudhashrestha requested a review from hari June 12, 2026 10:23
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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.

Changes

CardStack Core Refactor & Profile Extraction

Layer / File(s) Summary
CardStack generic typing and motion element modernization
animata/card/card-stack.tsx
CardStackItem reduced to id-only; CardStackContextValue, CardStackRootProps, CardStackListProps, and useCardStack made generic over T extends CardStackItem. Motion elements changed from article to div. onItemsChange callback updated with explicit T[] cast.
CardStackTrigger refactor with full overlay mode
animata/card/card-stack.tsx
New CardStackTriggerProps interface and useCardStackTriggerHandlers helper centralize pointer/keyboard press state. full mode renders focusable div with role="button" instead of button element.
CardStackProfileCard and profile-specific UI components
animata/card/card-stack-profile.tsx
CardStackProfileItem interface extends CardStackItem with image/title/tagline/optional counts/maskId. Exports CARD_STACK_MASK_IDS constant, CardStackProfileLiveRegion (announcements), structural components (header/avatar/meta), CardStackProfileMedia (masked image in figure), CardStackProfileMetric (icon+value), and CardStackProfileCard (full card demo wrapper).
Story and bento demo refactored to use profile types and generics
animata/card/card-stack.stories.tsx, app/(main)/_landing/card-stack-bento.tsx
card-stack.stories.tsx updated demoCards to CardStackProfileItem, added StoryLiveRegion helper, rewrote Primary story to render CardStackProfileMasks/CardStackProfileLiveRegion outside trigger, uses CardStackProfileCard items. card-stack-bento.tsx updated to useCardStack<CardStackProfileItem>(), renders profile masks/live-region, uses full trigger.
Photographer portfolio and card-stack people updated to generic API
app/demo/library/hero/photographer-portfolio.tsx, app/demo/library/shared/card-stack-people.ts, app/demo/library/shared/profile-stack-card.tsx
photographer-portfolio.tsx introduces local PortfolioItem type, calls useCardStack<PortfolioItem>(), refactors PrintStack to section/Viewport/List with full trigger. card-stack-people.ts updated DEMO_PEOPLE/LOOKBOOK_ITEMS/GUEST_ITEMS to CardStackProfileItem[]. profile-stack-card.tsx refactored to single CardStackProfileCard render.

Four-Tier Changelog Documentation Framework

Layer / File(s) Summary
CLAUDE.md and contributing docs updated with four-tier framework
CLAUDE.md, content/docs/contributing/changelog.mdx, content/docs/contributing/components.mdx, content/docs/contributing/index.mdx
CLAUDE.md replaced changelog guidance with four-tier framework (site overview, monthly release, category index, component docs). content/docs/contributing/changelog.mdx added as comprehensive guide with tier definitions, templates, Tier 1 example, Tier 4 conventions, entry-warrant rules, and shipping checklist. components.mdx and index.mdx updated to reference tiers and user-visible fix guidance.
Release notes updates across tiers 1-3 and config
content/docs/changelog/index.mdx, content/docs/changelog/2026-06.mdx, content/docs/card/index.mdx, config/docs.ts, scripts/validate-registry-install.js
content/docs/changelog/index.mdx updated site overview and June highlights. 2026-06.mdx updated month description and added Card Stack entry. content/docs/card/index.mdx added Recent changes table. config/docs.ts added Changelog nav item. validate-registry-install.js updated fixture to expect card-stack.tsx and motion only.
Card Stack component documentation updated for registry trim and new API
content/docs/card/card-stack.mdx
Description simplified, installation trimmed to card-stack.tsx only, usage rewritten with stable id requirement and CardStack.List render props, full prop and aria-label documented, How it works section condensed, 2026-06-12 changelog entry updated (trimmed registry, profile UI to demo, full overlay, HTML fix).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • codse/animata#439: Overlaps CLAUDE.md changelog/release-note guidance updates and tiered changelog rules.
  • codse/animata#471: Prior Card Stack work — this PR’s generic refactor and trigger changes build on Card Stack surface edits from #471.

Suggested reviewers

  • hari

🐰 A rabbit's poetic tribute to the refactor

Generic items hop with graceful grace,
Profile cards find their rightful place,
Triggers overlay the screen anew,
Four-tier notes—a changelog too! 📚

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(card-stack): bare-bones API' accurately and specifically summarizes the main change—reducing Card Stack to core stack mechanics while moving profile UI elsewhere.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/card-stack-refactor

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Keep onItemsChange out of the setItemList updater.

rotateOne calls onItemsChange inside setItemList((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 invoke onItemsChange after commit (e.g., via an effect/ref based on the committed itemList).

  • Forward onKeyDown to the non-full trigger <button>.
    CardStackTrigger computes handlers.onKeyDown, but the full=false branch’s <button> doesn’t pass onKeyDown={handlers.onKeyDown}, so the consumer-provided onKeyDown is 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 win

Narrow PortfolioItem.id to PortfolioId.

After the core item was generalized, this local type widened id back to plain string, which is why PrintCaption now needs as PortfolioId and a fallback branch. Typing id as PortfolioId keeps PORTFOLIO and SHOT_SETTINGS in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 47b3438 and 0f8f39b.

⛔ Files ignored due to path filters (1)
  • app/demo/generated/demo-sources.ts is excluded by !**/generated/**
📒 Files selected for processing (17)
  • CLAUDE.md
  • animata/card/card-stack-profile.tsx
  • animata/card/card-stack.stories.tsx
  • animata/card/card-stack.tsx
  • app/(main)/_landing/card-stack-bento.tsx
  • app/demo/library/hero/photographer-portfolio.tsx
  • app/demo/library/shared/card-stack-people.ts
  • app/demo/library/shared/profile-stack-card.tsx
  • config/docs.ts
  • content/docs/card/card-stack.mdx
  • content/docs/card/index.mdx
  • content/docs/changelog/2026-06.mdx
  • content/docs/changelog/index.mdx
  • content/docs/contributing/changelog.mdx
  • content/docs/contributing/components.mdx
  • content/docs/contributing/index.mdx
  • scripts/validate-registry-install.js

Comment thread animata/card/card-stack.tsx
Comment thread app/(main)/_landing/card-stack-bento.tsx Outdated
Comment thread app/demo/library/shared/profile-stack-card.tsx
Comment thread content/docs/changelog/index.mdx Outdated
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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 12, 2026

Copy link
Copy Markdown

Deploying animata with  Cloudflare Pages  Cloudflare Pages

Latest commit: f655947
Status: ✅  Deploy successful!
Preview URL: https://21b241ce.animata.pages.dev
Branch Preview URL: https://feat-card-stack-refactor.animata.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Use a real <button> for the full overlay path.

The full branch reimplements button behavior on a div, so Enter/Space advances the stack without ever hitting the consumer onClick callback. Pointer activation and the non-full branch do call onClick, so the trigger API behaves differently for keyboard users in full mode.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0f8f39b and f655947.

⛔ Files ignored due to path filters (1)
  • app/demo/generated/demo-sources.ts is excluded by !**/generated/**
📒 Files selected for processing (5)
  • animata/card/card-stack.tsx
  • app/(main)/_landing/card-stack-bento.tsx
  • app/demo/library/hero/photographer-portfolio.tsx
  • app/demo/library/shared/profile-stack-card.tsx
  • content/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

@hari hari merged commit b38a676 into main Jun 12, 2026
6 checks passed
@hari hari deleted the feat/card-stack-refactor branch June 12, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants