Skip to content

feat(overlay): add animated DropdownMenu#456

Open
leazi99 wants to merge 5 commits intocodse:mainfrom
leazi99:feat/overlay-dropdown-menu
Open

feat(overlay): add animated DropdownMenu#456
leazi99 wants to merge 5 commits intocodse:mainfrom
leazi99:feat/overlay-dropdown-menu

Conversation

@leazi99
Copy link
Copy Markdown

@leazi99 leazi99 commented Apr 29, 2026

Adds animated DropdownMenu component, stories, and docs. Updates MDX preview sizing and exposes the component in mdx-components. Includes accessibility and lint fixes.

Summary by CodeRabbit

  • New Features

    • Added State Action Card component with interactive actions and animated feedback.
    • Added Swipe Deck carousel component for horizontally scrollable cards with drag support.
    • Added Dropdown Menu component with keyboard navigation and ARIA accessibility.
    • Added Pricing Comparison component featuring plan selection and feature comparison table.
  • Documentation

    • Added documentation and usage guides for new components.
  • Chores

    • Updated GitHub Actions workflow dependencies.

Copilot AI review requested due to automatic review settings April 29, 2026 06:21
@leazi99 leazi99 requested a deployment to preview-deployment April 29, 2026 06:21 — with GitHub Actions Waiting
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

This PR introduces four new interactive React components—StateActionCard, SwipeDeck, DropdownMenu, and PricingComparison—each with Storybook stories and MDX documentation. It refactors component preview and MDX rendering utilities, upgrades GitHub Actions dependencies in workflows, and replaces Balancer with a plain div in ProductFeatures.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/deploy-v3.yml, .github/workflows/deploy.yml
Upgraded actions/checkout (v4→v6), actions/setup-node (v4→v6), and actions/cache (v4→v5); added FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 environment variable at job level.
StateActionCard Component
animata/card/state-action-card.tsx, animata/card/state-action-card.stories.tsx, content/docs/card/state-action-card.mdx
New client component rendering animated cards with preset use cases (task/social/order), interactive action buttons (favorite/complete/share) with state management, animated feedback indicators, and confetti burst on completion; includes 3 Storybook stories and documentation.
SwipeDeck Component
animata/carousel/swipe-deck.tsx, animata/carousel/swipe-deck.stories.tsx, content/docs/carousel/swipe-deck.mdx
New horizontal snap-scrolling carousel component with parallax layer support, pointer/keyboard navigation, optional arrows and indicator dots, active index tracking, and smooth scroll-to-index behavior; includes story and documentation.
DropdownMenu Component
animata/overlay/dropdown-menu.tsx, animata/overlay/dropdown-menu.stories.tsx, content/docs/overlay/dropdown-menu.mdx
New animated dropdown menu with full keyboard navigation (arrows, Home/End, Enter/Space/Escape), click/hover trigger modes, focus management, ARIA attributes, item selection tracking, and optional link/callback support; includes 2 Storybook stories and documentation.
PricingComparison Component
animata/section/pricing-comparison.tsx, animata/section/pricing-comparison.stories.tsx, content/docs/section/pricing-comparison.mdx
New pricing table component with plan cards, billing cycle toggle, customizable selection callbacks, responsive feature comparison matrix with icon/text mapping, and highlighted plan/feature cell styling; includes 2 Storybook stories with extended enterprise plan variant and documentation.
Utility Refactoring
components/component-preview.tsx, components/mdx-components.tsx
Refactored ComponentPreview spinner references and control rendering logic into helper, tightened prop typing with Readonly<> wrappers, improved string processing with replaceAll, adjusted container styling (min-heights, overflow, alignment); enhanced mdx-components with DropdownMenu support, extracted post-processing logic, improved code snippet parsing, and made props read-only.
Minor Updates
animata/hero/product-features.tsx
Replaced Balancer component wrapper with plain div for subtitle rendering.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Issue #453: Addresses the implementation of a DropdownMenu component with keyboard navigation, focus management, and ARIA accessibility features, though positioned under overlay rather than navigation namespace.

Possibly related PRs

  • PR #424: Modifies components/component-preview.tsx with overlapping refactoring of story rendering and control logic.
  • PR #443: Modifies components/mdx-components.tsx to extend the MDX component map with additional renderers.
  • PR #425: Modifies components/component-preview.tsx with story preview selection and rendering refactors.

Suggested reviewers

  • sudhashrestha

Poem

🐰 Four new cards and carousels hop into view,
Dropdowns dance and prices sparkle anew,
With animations twirling and confetti to fly,
The UI burrows deeper—components multiply!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 PR title 'feat(overlay): add animated DropdownMenu' accurately reflects the main change: adding a new DropdownMenu component to the overlay category with animation support.
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 unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/overlay-dropdown-menu

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (2)
.github/workflows/deploy-v3.yml (1)

16-24: Pin GitHub Actions to immutable SHAs instead of floating major tags.

Using @v6 / @v5 is mutable and increases supply-chain risk. Prefer full commit SHAs for actions/checkout, actions/setup-node, and actions/cache.

Suggested hardening diff
-      - name: Checkout
-        uses: actions/checkout@v6
+      - name: Checkout
+        uses: actions/checkout@<full-length-commit-sha>

-      - name: Setup Node
-        uses: actions/setup-node@v6
+      - name: Setup Node
+        uses: actions/setup-node@<full-length-commit-sha>

-      - name: Restore cache
-        uses: actions/cache@v5
+      - name: Restore cache
+        uses: actions/cache@<full-length-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-v3.yml around lines 16 - 24, The workflow currently
uses mutable action tags (actions/checkout@v6, actions/setup-node@v6,
actions/cache@v5); replace each floating major tag with the corresponding
immutable full commit SHA for those actions (e.g., use
actions/checkout@<full-commit-sha>, actions/setup-node@<full-commit-sha>,
actions/cache@<full-commit-sha>) to pin versions, updating the steps that
reference those symbols in the deploy-v3.yml workflow.
animata/card/state-action-card.tsx (1)

98-121: Timeouts should be cleaned up on unmount.

The window.setTimeout calls in triggerActionFeedback and onComplete aren't cleaned up if the component unmounts, which could cause state updates on unmounted components.

♻️ Suggested fix using refs for cleanup
+  const feedbackTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const confettiTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+
+  useEffect(() => {
+    return () => {
+      if (feedbackTimeoutRef.current) clearTimeout(feedbackTimeoutRef.current);
+      if (confettiTimeoutRef.current) clearTimeout(confettiTimeoutRef.current);
+    };
+  }, []);
+
   const triggerActionFeedback = (action: ActionType) => {
     setLastAction(action);
-    window.setTimeout(() => {
+    if (feedbackTimeoutRef.current) clearTimeout(feedbackTimeoutRef.current);
+    feedbackTimeoutRef.current = setTimeout(() => {
       setLastAction((previous) => (previous === action ? null : previous));
     }, 800);
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@animata/card/state-action-card.tsx` around lines 98 - 121, The component uses
window.setTimeout in triggerActionFeedback and onComplete without cleanup; store
the timeout IDs in refs (e.g., feedbackTimerRef and confettiTimerRef) when
calling window.setTimeout inside triggerActionFeedback and onComplete, use
window.clearTimeout to cancel any existing timer before assigning a new one, and
add a useEffect cleanup that clears both refs on unmount so no setState runs
after the component has been unmounted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/deploy.yml:
- Around line 18-20: The workflow currently checks out pull request head from
forks with privileged secrets available; update the job condition that uses
github.event.pull_request.head.repo.full_name so it only runs for the
same-repository PRs (change the negated check to an equality) and stop checking
out untrusted fork code in the privileged job (the checkout step referencing
actions/checkout@v6 and ref: ${{ github.event.pull_request.head.sha }}). If fork
previews are needed, split into an unprivileged pull_request job (no deployment
secrets) and a separate maintainer-approved privileged deploy job that uses
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID / GITHUB_TOKEN only after manual
approval.

In `@animata/card/state-action-card.tsx`:
- Around line 128-244: The component lacks dark-mode class variants; update the
style classNames used in motion.article, the CardIcon wrapper span, the
title/description text, the status badges rendered from statuses, and the
ActionButton usages to include appropriate dark: variants (e.g., add
dark:bg-zinc-900 alongside bg-white on motion.article; add dark:bg-zinc-800 to
the CardIcon container; add dark:text-zinc-100 for
preset.title/preset.description; ensure each status.className from statuses
includes matching dark classes or map a darkClass for each status; and pass
dark-aware className props to ActionButton instances used for Heart,
CheckCircle2, Share2) so the card renders correctly in both light and dark
themes.

In `@animata/carousel/swipe-deck.tsx`:
- Around line 184-205: The card and its child elements use hard-coded light
colors (e.g., the outer div with className "bg-stone-100", overlay
"bg-linear-to-t from-black/65...", badge styles, title "text-stone-900",
description "text-stone-700") and similarly for the arrows/indicators later;
replace these with semantic theme tokens or add Tailwind dark: variants so the
component is dark-mode aware. Update the card container (the div with className
starting "relative w-[82%]... bg-stone-100"), the overlay layer
(data-parallax-layer and the "bg-linear-to-t" overlay), the badge span, the
h3/title and p/description classes, and the arrow/indicator elements referenced
later to use semantic classes (e.g., bg-card, text-primary, border-muted) or add
appropriate dark: classes (e.g., dark:bg-*, dark:text-*, dark:border-*) to
preserve contrast in both themes. Ensure all color tokens used are from the
shared design tokens so styles remain consistent across light and dark modes.

In `@animata/overlay/dropdown-menu.tsx`:
- Around line 378-392: The onMouseEnter handler is setting activeIndex for
disabled items (item.disabled) which causes them to be highlighted and
referenced by aria-activedescendant; remove or guard the call to setActiveIndex
inside the disabled branch so disabled items do not call setActiveIndex on hover
(i.e., only attach or invoke setActiveIndex in the non-disabled rendering path
or wrap the onMouseEnter with a check !item.disabled), and keep the disabled
branch rendering (the block using key={itemId} id={itemId} role="menuitem"
aria-disabled="true" tabIndex={-1} ...) free of any onMouseEnter that updates
activeIndex.

In `@components/component-preview.tsx`:
- Around line 108-125: The boolean toggle currently renders as a plain <button>
in the component-preview (the button that uses id={`prop-${key}`} and
onClick={() => onChange(key, !value)}) but does not expose its state to
assistive tech; update that element to include accessible state attributes such
as aria-pressed={value} (or role="switch" plus aria-checked={value}) and ensure
the label/aria-label conveys the prop name (use key) so screen readers know
which prop is toggled and its current on/off state.

In `@components/mdx-components.tsx`:
- Around line 53-60: The code uses a non-global regex named regex =
/event="([^"]*)"/ and then calls meta.replaceAll(regex, ""), which throws a
TypeError; update the replacement to either use a global regex (e.g., add the /g
flag to regex) or call meta.replace(regex, "") so replaceAll isn't invoked with
a non-global RegExp — change the logic around the match handling in the block
that sets (node as UnistNode & { __event__?: string }) and updates (codeEl as
UnistNode & { data?: { meta?: string } }).data!.meta accordingly.

In `@content/docs/carousel/swipe-deck.mdx`:
- Around line 4-5: Update the frontmatter author value to match the credits
section: change the frontmatter key "author: copilot" so it reads the same as
the credits line "Built by [Ujjwal Basnet]" (e.g., author: "Ujjwal Basnet") or
update the credits block to credit "copilot"—edit the frontmatter `author` field
or the credits section accordingly so both refer to the same author name.

In `@content/docs/overlay/dropdown-menu.mdx`:
- Line 59: Remove the stray closing bracket character ']' that appears at the
end of the dropdown-menu.mdx file; locate the trailing ']' (end of file) and
delete it so the MDX content ends cleanly without the extra character.
- Line 4: The frontmatter author value is a placeholder ("YourTwitterUsername");
update the author field in the dropdown-menu.mdx frontmatter to the correct
contributor identifier shown in the credits (replace "YourTwitterUsername" with
the actual author handle/name from the credits section) so the metadata
accurately reflects the page author.

In `@content/docs/section/pricing-comparison.mdx`:
- Around line 19-25: Replace the manual file-creation step in the
pricing-comparison section with the shadcn registry install command: locate the
<Step> block that currently instructs users to run the mkdir/touch command for
components/animata/section/pricing-comparison.tsx and change it to instruct
running the registry URL install for section/pricing-comparison (use the shadcn
registry URL format https://animata.design/r/section/pricing-comparison.json and
the npx shadcn@latest add flow) so the docs follow the required pattern.

---

Nitpick comments:
In @.github/workflows/deploy-v3.yml:
- Around line 16-24: The workflow currently uses mutable action tags
(actions/checkout@v6, actions/setup-node@v6, actions/cache@v5); replace each
floating major tag with the corresponding immutable full commit SHA for those
actions (e.g., use actions/checkout@<full-commit-sha>,
actions/setup-node@<full-commit-sha>, actions/cache@<full-commit-sha>) to pin
versions, updating the steps that reference those symbols in the deploy-v3.yml
workflow.

In `@animata/card/state-action-card.tsx`:
- Around line 98-121: The component uses window.setTimeout in
triggerActionFeedback and onComplete without cleanup; store the timeout IDs in
refs (e.g., feedbackTimerRef and confettiTimerRef) when calling
window.setTimeout inside triggerActionFeedback and onComplete, use
window.clearTimeout to cancel any existing timer before assigning a new one, and
add a useEffect cleanup that clears both refs on unmount so no setState runs
after the component has been unmounted.
🪄 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: 38926577-dd13-4909-9e05-40ee974ffb5e

📥 Commits

Reviewing files that changed from the base of the PR and between a5bebcd and 926f725.

📒 Files selected for processing (17)
  • .github/workflows/deploy-v3.yml
  • .github/workflows/deploy.yml
  • animata/card/state-action-card.stories.tsx
  • animata/card/state-action-card.tsx
  • animata/carousel/swipe-deck.stories.tsx
  • animata/carousel/swipe-deck.tsx
  • animata/hero/product-features.tsx
  • animata/overlay/dropdown-menu.stories.tsx
  • animata/overlay/dropdown-menu.tsx
  • animata/section/pricing-comparison.stories.tsx
  • animata/section/pricing-comparison.tsx
  • components/component-preview.tsx
  • components/mdx-components.tsx
  • content/docs/card/state-action-card.mdx
  • content/docs/carousel/swipe-deck.mdx
  • content/docs/overlay/dropdown-menu.mdx
  • content/docs/section/pricing-comparison.mdx

Comment on lines +18 to 20
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Detect risky combination: pull_request_target + checkout of PR head + secrets usage
rg -n -C2 'pull_request_target|ref:\s*\$\{\{\s*github\.event\.pull_request\.head\.sha\s*\}\}|apiToken:\s*\$\{\{\s*secrets\.' .github/workflows

Repository: codse/animata

Length of output: 1471


🏁 Script executed:

cat -n .github/workflows/deploy.yml

Repository: codse/animata

Length of output: 2983


Disable fork execution in this privileged workflow.

Line 11 explicitly runs this job only for forks (!= github.repository), while checking out untrusted fork code at line 20 with access to deployment secrets (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, GITHUB_TOKEN). This is a critical secret-exfiltration vector.

Change line 11 from:

    if: github.event.pull_request.head.repo.full_name != github.repository

to:

    if: github.event.pull_request.head.repo.full_name == github.repository

This restricts preview deployment to internal PRs only. If fork previews are required, split into an unprivileged pull_request build and a maintainer-approved privileged deploy path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy.yml around lines 18 - 20, The workflow currently
checks out pull request head from forks with privileged secrets available;
update the job condition that uses github.event.pull_request.head.repo.full_name
so it only runs for the same-repository PRs (change the negated check to an
equality) and stop checking out untrusted fork code in the privileged job (the
checkout step referencing actions/checkout@v6 and ref: ${{
github.event.pull_request.head.sha }}). If fork previews are needed, split into
an unprivileged pull_request job (no deployment secrets) and a separate
maintainer-approved privileged deploy job that uses CLOUDFLARE_API_TOKEN /
CLOUDFLARE_ACCOUNT_ID / GITHUB_TOKEN only after manual approval.

Comment on lines +128 to +244
return (
<motion.article
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.35 }}
className={cn(
"group relative w-full max-w-sm overflow-hidden rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_16px_45px_-24px_rgba(0,0,0,0.45)]",
className,
)}
>
<div className="absolute inset-x-0 top-0 h-1 bg-linear-to-r from-cyan-500 via-emerald-500 to-fuchsia-500" />

<div className="mb-4 flex items-start justify-between gap-3">
<div className="flex items-center gap-3">
<span className="rounded-xl bg-zinc-100 p-2 text-zinc-700">
<CardIcon className="size-4" />
</span>
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-zinc-500">
Interactive Card
</p>
</div>
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-700">
<Sparkles className="size-3" />
Live State
</span>
</div>

<h3 className="text-xl font-semibold text-zinc-900">{preset.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-zinc-600">{preset.description}</p>

<div className="mt-4 flex flex-wrap gap-2">
{statuses.map((status) => (
<span
key={status.label}
className={cn("rounded-full px-2.5 py-1 text-xs font-medium", status.className)}
>
{status.label}
</span>
))}
</div>

<div className="mt-5 flex items-center justify-between">
<p className="text-sm font-medium text-zinc-500">{preset.meta}</p>

<AnimatePresence mode="wait" initial={false}>
{lastAction && (
<motion.div
key={lastAction}
initial={{ opacity: 0, y: 8, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -8, scale: 0.95 }}
transition={{ duration: 0.18 }}
className="inline-flex items-center gap-1 rounded-full bg-emerald-600 px-2.5 py-1 text-xs font-semibold text-white"
>
<Check className="size-3.5" />
Action saved
</motion.div>
)}
</AnimatePresence>
</div>

<div className="pointer-events-none mt-4 h-0.5 bg-linear-to-r from-transparent via-zinc-200 to-transparent" />

<div
className={cn(
"mt-4 flex items-center gap-2 transition-all duration-300",
"opacity-100 translate-y-0 sm:translate-y-3 sm:opacity-0 sm:group-hover:translate-y-0 sm:group-hover:opacity-100 sm:group-focus-within:translate-y-0 sm:group-focus-within:opacity-100",
)}
>
<ActionButton
icon={Heart}
onClick={onFavorite}
label={isFavorite ? "Favorited" : "Add to favorites"}
active={isFavorite}
/>

<ActionButton
icon={CheckCircle2}
onClick={onComplete}
label={isCompleted ? "Completed" : "Mark complete"}
active={isCompleted}
/>

<ActionButton
icon={Share2}
onClick={onShare}
label={isShared ? "Shared" : "Share"}
active={isShared}
/>
</div>

<AnimatePresence>
{showConfetti && (
<motion.div
className="pointer-events-none absolute left-1/2 top-[52%]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{confettiPieces.map((piece) => (
<motion.span
key={piece.id}
className={cn("absolute h-2 w-1.5 rounded-sm", piece.color)}
initial={{ x: 0, y: 0, rotate: 0, opacity: 1 }}
animate={{
x: piece.x,
y: piece.y,
rotate: piece.rotate,
opacity: 0,
}}
transition={{ duration: 0.75, ease: "easeOut" }}
/>
))}
</motion.div>
)}
</AnimatePresence>
</motion.article>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing dark mode support violates theme-responsive requirement.

The component uses only light-mode styles (bg-white, text-zinc-900, bg-zinc-100, etc.) without any dark: variants. All new components must be theme-responsive.

Key areas needing dark mode variants:

  • Line 134: bg-white → add dark:bg-zinc-900
  • Line 142: bg-zinc-100 → add dark:bg-zinc-800
  • Line 155: text-zinc-900 → add dark:text-zinc-100
  • Status badges (lines 85-94): add dark variants for each state
  • Action buttons (lines 260-265): add dark variants

As per coding guidelines: "All new components must be theme-responsive (light + dark mode support)"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@animata/card/state-action-card.tsx` around lines 128 - 244, The component
lacks dark-mode class variants; update the style classNames used in
motion.article, the CardIcon wrapper span, the title/description text, the
status badges rendered from statuses, and the ActionButton usages to include
appropriate dark: variants (e.g., add dark:bg-zinc-900 alongside bg-white on
motion.article; add dark:bg-zinc-800 to the CardIcon container; add
dark:text-zinc-100 for preset.title/preset.description; ensure each
status.className from statuses includes matching dark classes or map a darkClass
for each status; and pass dark-aware className props to ActionButton instances
used for Heart, CheckCircle2, Share2) so the card renders correctly in both
light and dark themes.

Comment on lines +184 to +205
className="relative w-[82%] shrink-0 snap-center overflow-hidden rounded-2xl border border-black/10 bg-stone-100 shadow-[0_12px_30px_-16px_rgba(0,0,0,0.45)] sm:w-[72%] lg:w-[64%]"
>
<div className="relative h-56 overflow-hidden sm:h-64">
<div
data-parallax-layer
className="absolute inset-0 bg-cover bg-center transition-transform duration-200 ease-out will-change-transform"
style={{ backgroundImage: `url(${item.image})` }}
/>
<div className="absolute inset-0 bg-linear-to-t from-black/65 via-black/15 to-transparent" />
{item.badge && (
<span className="absolute left-4 top-4 rounded-full bg-amber-300 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-amber-950">
{item.badge}
</span>
)}
</div>

<div className="space-y-2 p-5 sm:p-6">
<h3 className="text-balance text-xl font-semibold leading-tight text-stone-900 sm:text-2xl">
{item.title}
</h3>
<p className="text-sm leading-relaxed text-stone-700 sm:text-base">
{item.description}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the component dark-mode aware.

The card surface, text, arrows, and indicators are all hard-coded to light-theme colors, so this new component will look out of place and lose contrast in dark mode. Please swap these to semantic tokens or add dark variants before shipping.

As per coding guidelines, "All new components must be theme-responsive (light + dark mode support)".

Also applies to: 289-321

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@animata/carousel/swipe-deck.tsx` around lines 184 - 205, The card and its
child elements use hard-coded light colors (e.g., the outer div with className
"bg-stone-100", overlay "bg-linear-to-t from-black/65...", badge styles, title
"text-stone-900", description "text-stone-700") and similarly for the
arrows/indicators later; replace these with semantic theme tokens or add
Tailwind dark: variants so the component is dark-mode aware. Update the card
container (the div with className starting "relative w-[82%]... bg-stone-100"),
the overlay layer (data-parallax-layer and the "bg-linear-to-t" overlay), the
badge span, the h3/title and p/description classes, and the arrow/indicator
elements referenced later to use semantic classes (e.g., bg-card, text-primary,
border-muted) or add appropriate dark: classes (e.g., dark:bg-*, dark:text-*,
dark:border-*) to preserve contrast in both themes. Ensure all color tokens used
are from the shared design tokens so styles remain consistent across light and
dark modes.

Comment on lines +378 to +392
if (item.disabled) {
return (
<div
key={itemId}
id={itemId}
role="menuitem"
aria-disabled="true"
tabIndex={-1}
data-active={isActive}
className={sharedClassName}
onMouseEnter={() => setActiveIndex(index)}
>
{content}
</div>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Disabled items should not become active on hover.

Disabled menu items currently update activeIndex on onMouseEnter (line 388), which causes them to be visually highlighted and sets aria-activedescendant to point to a disabled item. This is inconsistent with standard menu semantics where disabled items are typically skipped.

🐛 Proposed fix
                   <div
                     key={itemId}
                     id={itemId}
                     role="menuitem"
                     aria-disabled="true"
                     tabIndex={-1}
                     data-active={isActive}
                     className={sharedClassName}
-                    onMouseEnter={() => setActiveIndex(index)}
                   >
                     {content}
                   </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (item.disabled) {
return (
<div
key={itemId}
id={itemId}
role="menuitem"
aria-disabled="true"
tabIndex={-1}
data-active={isActive}
className={sharedClassName}
onMouseEnter={() => setActiveIndex(index)}
>
{content}
</div>
);
if (item.disabled) {
return (
<div
key={itemId}
id={itemId}
role="menuitem"
aria-disabled="true"
tabIndex={-1}
data-active={isActive}
className={sharedClassName}
>
{content}
</div>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@animata/overlay/dropdown-menu.tsx` around lines 378 - 392, The onMouseEnter
handler is setting activeIndex for disabled items (item.disabled) which causes
them to be highlighted and referenced by aria-activedescendant; remove or guard
the call to setActiveIndex inside the disabled branch so disabled items do not
call setActiveIndex on hover (i.e., only attach or invoke setActiveIndex in the
non-disabled rendering path or wrap the onMouseEnter with a check
!item.disabled), and keep the disabled branch rendering (the block using
key={itemId} id={itemId} role="menuitem" aria-disabled="true" tabIndex={-1} ...)
free of any onMouseEnter that updates activeIndex.

Comment on lines +108 to +125
if (typeof value === "boolean") {
return (
<button
id={`prop-${key}`}
type="button"
onClick={() => onChange(key, !value)}
className={cn(
"h-5 w-9 rounded-full transition-colors",
value ? "bg-primary" : "bg-muted-foreground/30",
)}
>
<div
className={cn(
"h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
value ? "translate-x-4.5" : "translate-x-0.5",
)}
/>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Expose the boolean toggle state to assistive tech.

This renders as a plain button today, so the current on/off state is only visual. Adding aria-pressed={value} (or switch semantics) would make the props editor stateful for screen-reader users too.

Suggested fix
         <button
           id={`prop-${key}`}
           type="button"
+          aria-pressed={value}
           onClick={() => onChange(key, !value)}
           className={cn(
             "h-5 w-9 rounded-full transition-colors",
             value ? "bg-primary" : "bg-muted-foreground/30",
           )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof value === "boolean") {
return (
<button
id={`prop-${key}`}
type="button"
onClick={() => onChange(key, !value)}
className={cn(
"h-5 w-9 rounded-full transition-colors",
value ? "bg-primary" : "bg-muted-foreground/30",
)}
>
<div
className={cn(
"h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
value ? "translate-x-4.5" : "translate-x-0.5",
)}
/>
</button>
if (typeof value === "boolean") {
return (
<button
id={`prop-${key}`}
type="button"
aria-pressed={value}
onClick={() => onChange(key, !value)}
className={cn(
"h-5 w-9 rounded-full transition-colors",
value ? "bg-primary" : "bg-muted-foreground/30",
)}
>
<div
className={cn(
"h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
value ? "translate-x-4.5" : "translate-x-0.5",
)}
/>
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/component-preview.tsx` around lines 108 - 125, The boolean toggle
currently renders as a plain <button> in the component-preview (the button that
uses id={`prop-${key}`} and onClick={() => onChange(key, !value)}) but does not
expose its state to assistive tech; update that element to include accessible
state attributes such as aria-pressed={value} (or role="switch" plus
aria-checked={value}) and ensure the label/aria-label conveys the prop name (use
key) so screen readers know which prop is toggled and its current on/off state.

Comment on lines 53 to +60
const regex = /event="([^"]*)"/;
const match = meta.match(regex);
const match = regex.exec(meta);
if (match) {
(node as UnistNode & { __event__?: string }).__event__ = match[1];
(codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replace(regex, "");
(codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replaceAll(
regex,
"",
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
const meta = 'event="copy"';
const regex = /event="([^"]*)"/;
try {
  console.log(meta.replaceAll(regex, ""));
} catch (error) {
  console.error(`${error.name}: ${error.message}`);
  process.exit(1);
}
NODE

Repository: codse/animata

Length of output: 137


🏁 Script executed:

cat -n components/mdx-components.tsx | sed -n '45,70p'

Repository: codse/animata

Length of output: 1125


replaceAll will throw with this regex.

regex is not global, so meta.replaceAll(regex, "") on line 57 raises a TypeError as soon as a code fence includes event="...". Use replace(...) instead or add the /g flag to the regex.

Suggested fix
         const regex = /event="([^"]*)"/;
         const match = regex.exec(meta);
         if (match) {
           (node as UnistNode & { __event__?: string }).__event__ = match[1];
-          (codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replaceAll(
+          (codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replace(
             regex,
             "",
           );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const regex = /event="([^"]*)"/;
const match = meta.match(regex);
const match = regex.exec(meta);
if (match) {
(node as UnistNode & { __event__?: string }).__event__ = match[1];
(codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replace(regex, "");
(codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replaceAll(
regex,
"",
);
const regex = /event="([^"]*)"/;
const match = regex.exec(meta);
if (match) {
(node as UnistNode & { __event__?: string }).__event__ = match[1];
(codeEl as UnistNode & { data?: { meta?: string } }).data!.meta = meta.replace(
regex,
"",
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/mdx-components.tsx` around lines 53 - 60, The code uses a
non-global regex named regex = /event="([^"]*)"/ and then calls
meta.replaceAll(regex, ""), which throws a TypeError; update the replacement to
either use a global regex (e.g., add the /g flag to regex) or call
meta.replace(regex, "") so replaceAll isn't invoked with a non-global RegExp —
change the logic around the match handling in the block that sets (node as
UnistNode & { __event__?: string }) and updates (codeEl as UnistNode & { data?:
{ meta?: string } }).data!.meta accordingly.

Comment on lines +4 to +5
author: copilot
labels: ["requires interaction", "touch", "drag", "swipe"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Author field inconsistency with credits section.

The frontmatter author: copilot doesn't match the credits section which states "Built by [Ujjwal Basnet]". Consider aligning these for consistency.

📝 Suggested fix
 ---
 title: Swipe Deck
 description: A horizontal swipeable card deck with snap scrolling, indicators, arrows, and parallax motion.
-author: copilot
+author: leazi99
 labels: ["requires interaction", "touch", "drag", "swipe"]
 published: true
 ---
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
author: copilot
labels: ["requires interaction", "touch", "drag", "swipe"]
---
title: Swipe Deck
description: A horizontal swipeable card deck with snap scrolling, indicators, arrows, and parallax motion.
author: leazi99
labels: ["requires interaction", "touch", "drag", "swipe"]
published: true
---
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content/docs/carousel/swipe-deck.mdx` around lines 4 - 5, Update the
frontmatter author value to match the credits section: change the frontmatter
key "author: copilot" so it reads the same as the credits line "Built by [Ujjwal
Basnet]" (e.g., author: "Ujjwal Basnet") or update the credits block to credit
"copilot"—edit the frontmatter `author` field or the credits section accordingly
so both refer to the same author name.

---
title: Dropdown Menu
description: A flexible dropdown menu with smooth entrance and exit motion, keyboard navigation, and accessible menu semantics.
author: YourTwitterUsername
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace placeholder author.

The author field contains YourTwitterUsername which appears to be a placeholder. Based on the credits section, this should be updated.

📝 Suggested fix
 ---
 title: Dropdown Menu
 description: A flexible dropdown menu with smooth entrance and exit motion, keyboard navigation, and accessible menu semantics.
-author: YourTwitterUsername
+author: leazi99
 ---
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
author: YourTwitterUsername
author: leazi99
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content/docs/overlay/dropdown-menu.mdx` at line 4, The frontmatter author
value is a placeholder ("YourTwitterUsername"); update the author field in the
dropdown-menu.mdx frontmatter to the correct contributor identifier shown in the
credits (replace "YourTwitterUsername" with the actual author handle/name from
the credits section) so the metadata accurately reflects the page author.

## Credits

Built by [Ujjwal Basnet](https://github.com/leazi99)
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove stray bracket character.

There's a stray ] character at the end of the file that appears to be a typo.

📝 Suggested fix
 Built by [Ujjwal Basnet](https://github.com/leazi99)
-]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
]
Built by [Ujjwal Basnet](https://github.com/leazi99)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content/docs/overlay/dropdown-menu.mdx` at line 59, Remove the stray closing
bracket character ']' that appears at the end of the dropdown-menu.mdx file;
locate the trailing ']' (end of file) and delete it so the MDX content ends
cleanly without the extra character.

Comment on lines +19 to +25
<Step>Run the following command</Step>

It will create a new file `pricing-comparison.tsx` inside the `components/animata/section` directory.

```bash
mkdir -p components/animata/section && touch components/animata/section/pricing-comparison.tsx
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the registry install flow here.

This page bypasses the required docs pattern and asks users to create the file manually. Please switch this step to the registry URL install command for section/pricing-comparison so the docs stay consistent with the rest of the site.

Suggested doc update
-<Step>Run the following command</Step>
-
-It will create a new file `pricing-comparison.tsx` inside the `components/animata/section` directory.
-
-```bash
-mkdir -p components/animata/section && touch components/animata/section/pricing-comparison.tsx
-```
+<Step>Run the following command</Step>
+
+```bash
+npx shadcn@latest add https://animata.design/r/section/pricing-comparison.json
+```

As per coding guidelines, "Use shadcn registry URL format https://animata.design/r/{category}/{name}.json in docs/changelog — never bare npx shadcn add component-name".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Step>Run the following command</Step>
It will create a new file `pricing-comparison.tsx` inside the `components/animata/section` directory.
```bash
mkdir -p components/animata/section && touch components/animata/section/pricing-comparison.tsx
```
<Step>Run the following command</Step>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@content/docs/section/pricing-comparison.mdx` around lines 19 - 25, Replace
the manual file-creation step in the pricing-comparison section with the shadcn
registry install command: locate the <Step> block that currently instructs users
to run the mkdir/touch command for
components/animata/section/pricing-comparison.tsx and change it to instruct
running the registry URL install for section/pricing-comparison (use the shadcn
registry URL format https://animata.design/r/section/pricing-comparison.json and
the npx shadcn@latest add flow) so the docs follow the required pattern.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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