feat(overlay): add animated DropdownMenu#456
Conversation
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 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 unit tests (beta)
⚔️ Resolve merge conflicts
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
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/@v5is mutable and increases supply-chain risk. Prefer full commit SHAs foractions/checkout,actions/setup-node, andactions/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.setTimeoutcalls intriggerActionFeedbackandonCompletearen'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
📒 Files selected for processing (17)
.github/workflows/deploy-v3.yml.github/workflows/deploy.ymlanimata/card/state-action-card.stories.tsxanimata/card/state-action-card.tsxanimata/carousel/swipe-deck.stories.tsxanimata/carousel/swipe-deck.tsxanimata/hero/product-features.tsxanimata/overlay/dropdown-menu.stories.tsxanimata/overlay/dropdown-menu.tsxanimata/section/pricing-comparison.stories.tsxanimata/section/pricing-comparison.tsxcomponents/component-preview.tsxcomponents/mdx-components.tsxcontent/docs/card/state-action-card.mdxcontent/docs/carousel/swipe-deck.mdxcontent/docs/overlay/dropdown-menu.mdxcontent/docs/section/pricing-comparison.mdx
| uses: actions/checkout@v6 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha }} |
There was a problem hiding this comment.
🧩 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/workflowsRepository: codse/animata
Length of output: 1471
🏁 Script executed:
cat -n .github/workflows/deploy.ymlRepository: 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.
| 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> |
There was a problem hiding this comment.
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→ adddark:bg-zinc-900 - Line 142:
bg-zinc-100→ adddark:bg-zinc-800 - Line 155:
text-zinc-900→ adddark: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.
| 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} |
There was a problem hiding this comment.
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.
| 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> | ||
| ); |
There was a problem hiding this comment.
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.
| 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.
| 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> |
There was a problem hiding this comment.
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.
| 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.
| 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, | ||
| "", | ||
| ); |
There was a problem hiding this comment.
🧩 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);
}
NODERepository: 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.
| 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.
| author: copilot | ||
| labels: ["requires interaction", "touch", "drag", "swipe"] |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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) | ||
| ] |
There was a problem hiding this comment.
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.
| ] | |
| 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.
| <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 | ||
| ``` |
There was a problem hiding this comment.
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.
| <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.
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
Documentation
Chores