Skip to content

feat: add animated tabs component#275

Open
liusheng22 wants to merge 1 commit intounovue:mainfrom
liusheng22:feat/animated-tabs
Open

feat: add animated tabs component#275
liusheng22 wants to merge 1 commit intounovue:mainfrom
liusheng22:feat/animated-tabs

Conversation

@liusheng22
Copy link
Copy Markdown
Contributor

@liusheng22 liusheng22 commented Apr 1, 2026

Summary by CodeRabbit

  • New Features

    • Added an animated tabs component with smooth transitions, hover-aware visuals, and customizable styling options
    • Supports both controlled and uncontrolled modes with flexible content slots
  • Documentation

    • Added comprehensive documentation in English, French, and Chinese with API reference and usage examples

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

📝 Walkthrough

Walkthrough

Introduces a new Animated Tabs component system to the inspira UI library, including the main AnimatedTabs component with hover-aware animation, a demo component, playground configuration, TypeScript type definitions, and comprehensive documentation in English, French, and Chinese.

Changes

Cohort / File(s) Summary
UI Components & Types
app/components/inspira/ui/animated-tabs/AnimatedTabs.vue, app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue, app/components/inspira/ui/animated-tabs/types.ts, app/components/inspira/ui/animated-tabs/index.ts
New animated tabs component with motion-based tab selection and fade-in panel rendering. Includes controlled/uncontrolled modes, hover state tracking, and optional slot-based content. Exports AnimatedTabs component and AnimatedTab type via index module.
Demo & Configuration
app/components/inspira/examples/animated-tabs/AnimatedTabsDemo.vue, app/components/inspira/configs/animated-tabs/AnimatedTabsConfig.vue
Demo component showcasing animated tabs with reactive model binding and slot-based content rendering. Config component integrates demo into the component playground.
Documentation
content/en/2.components/miscellaneous/animated-tabs.md, content/fr/2.components/miscellaneous/animated-tabs.md, content/cn/2.components/miscellaneous/animated-tabs.md
Documentation pages in three languages covering component API (props, events, slots), AnimatedTab type definition, layout requirements, and credits.

Poem

🐰 Tabs now dance with graceful flair,
Motion trails float through the air,
Hover states and slides so neat,
Animation makes it all complete!
✨ Slots bring freedom, props take flight!

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add animated tabs component' accurately and concisely describes the main change - introducing a new animated tabs component with all supporting files, types, and documentation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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: 2

🧹 Nitpick comments (2)
app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue (1)

14-14: Use props.active?.value for clearer active tab comparison.

Line 14's isActive predicate relies on props.tabs[0] being the active tab. The parent component (AnimatedTabs.vue) does guarantee this through explicit reordering via the reorderTabs() function—the selected tab is always placed at index 0 before being passed to this component. However, since props.active is explicitly passed and available (line 162 in parent), using props.active?.value instead of props.tabs[0]?.value would be clearer and not depend on the parent's reordering implementation details.

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

In `@app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue` at line
14, The isActive predicate in AnimatedTabsFadeInDiv.vue currently compares
tab.value to props.tabs[0]?.value which relies on parent reordering; change the
comparison to use props.active?.value instead (i.e., update the isActive
function) so it directly checks the explicitly passed active prop (props.active)
rather than props.tabs[0], ensuring clearer intent and decoupling from parent
reordering.
app/components/inspira/ui/animated-tabs/AnimatedTabs.vue (1)

123-154: Consider adding ARIA attributes for accessibility.

A tabs component in a UI library benefits from proper accessibility support. Adding ARIA attributes would improve usability for screen reader users.

Suggested accessibility improvements
     <div
+      role="tablist"
       :class="
         cn(
           'relative flex w-full max-w-full shrink-0 flex-row items-center justify-start overflow-auto [-ms-overflow-style:none] [-webkit-overflow-scrolling:touch] [perspective:1000px] [scrollbar-width:none] sm:overflow-visible [&::-webkit-scrollbar]:hidden',
           containerClassName,
         )
       "
     >
       <button
         v-for="(tab, idx) in tabs"
-        :key="tab.title"
+        :key="tab.value"
         type="button"
+        role="tab"
+        :aria-selected="active?.value === tab.value"
+        :tabindex="active?.value === tab.value ? 0 : -1"
         :class="cn('relative rounded-full px-4 py-2', tabClassName)"
         :style="{ transformStyle: 'preserve-3d' }"
         `@click`="selectTab(idx)"
       >

You may also want to add keyboard navigation (arrow keys) and aria-controls linking tabs to their panels in a follow-up.

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

In `@app/components/inspira/ui/animated-tabs/AnimatedTabs.vue` around lines 123 -
154, The tab buttons currently render without ARIA roles or selected state;
update the container around the v-for buttons to include role="tablist" and give
each button role="tab" and an aria-selected attribute bound to (active?.value
=== tab.value) and ensure each button has a unique id (e.g., derived from
tab.value or pillLayoutId + idx) and an aria-controls that points to the
corresponding panel id; also ensure selectTab(tabIndex) still gets called on
`@click` so activation logic remains unchanged and add aria-disabled when
appropriate. Keep keyboard navigation and panel-side aria-labelledby/ids for a
follow-up.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/inspira/ui/animated-tabs/AnimatedTabs.vue`:
- Around line 131-134: The v-for on the button in AnimatedTabs.vue uses
:key="tab.title" which can conflict for duplicate titles; change the key to use
the unique identifier :key="tab.value" (ensure the tabs array items have a value
property) — update the button v-for (v-for="(tab, idx) in tabs") to use
tab.value as the key and keep existing bindings like selection logic that
reference tab.value.

In `@app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue`:
- Around line 39-42: The fallback rendering of the dynamic component (the
<component :is="tab.content" v-if="tab.content" /> block in
AnimatedTabsFadeInDiv.vue) ignores tab.contentProps; update that component
invocation to forward the props by binding contentProps (e.g.,
:props="tab.contentProps" or v-bind="tab.contentProps") so the dynamic/fallback
content receives the documented props; ensure the binding is applied only when
tab.contentProps exists and keep the v-if="tab.content" check.

---

Nitpick comments:
In `@app/components/inspira/ui/animated-tabs/AnimatedTabs.vue`:
- Around line 123-154: The tab buttons currently render without ARIA roles or
selected state; update the container around the v-for buttons to include
role="tablist" and give each button role="tab" and an aria-selected attribute
bound to (active?.value === tab.value) and ensure each button has a unique id
(e.g., derived from tab.value or pillLayoutId + idx) and an aria-controls that
points to the corresponding panel id; also ensure selectTab(tabIndex) still gets
called on `@click` so activation logic remains unchanged and add aria-disabled
when appropriate. Keep keyboard navigation and panel-side aria-labelledby/ids
for a follow-up.

In `@app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue`:
- Line 14: The isActive predicate in AnimatedTabsFadeInDiv.vue currently
compares tab.value to props.tabs[0]?.value which relies on parent reordering;
change the comparison to use props.active?.value instead (i.e., update the
isActive function) so it directly checks the explicitly passed active prop
(props.active) rather than props.tabs[0], ensuring clearer intent and decoupling
from parent reordering.
🪄 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: a6d0973e-d6c9-47f7-ba75-062232a2cbe0

📥 Commits

Reviewing files that changed from the base of the PR and between 738ac57 and bd7acd5.

📒 Files selected for processing (9)
  • app/components/inspira/configs/animated-tabs/AnimatedTabsConfig.vue
  • app/components/inspira/examples/animated-tabs/AnimatedTabsDemo.vue
  • app/components/inspira/ui/animated-tabs/AnimatedTabs.vue
  • app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue
  • app/components/inspira/ui/animated-tabs/index.ts
  • app/components/inspira/ui/animated-tabs/types.ts
  • content/cn/2.components/miscellaneous/animated-tabs.md
  • content/en/2.components/miscellaneous/animated-tabs.md
  • content/fr/2.components/miscellaneous/animated-tabs.md

Comment on lines +131 to +134
<button
v-for="(tab, idx) in tabs"
:key="tab.title"
type="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

Use tab.value as the key instead of tab.title.

If multiple tabs share the same title, Vue will incorrectly reuse DOM elements, potentially causing animation glitches or state issues. The value property is the unique identifier used for selection and should be used for keying.

Proposed fix
       <button
         v-for="(tab, idx) in tabs"
-        :key="tab.title"
+        :key="tab.value"
         type="button"
📝 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
<button
v-for="(tab, idx) in tabs"
:key="tab.title"
type="button"
<button
v-for="(tab, idx) in tabs"
:key="tab.value"
type="button"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/inspira/ui/animated-tabs/AnimatedTabs.vue` around lines 131 -
134, The v-for on the button in AnimatedTabs.vue uses :key="tab.title" which can
conflict for duplicate titles; change the key to use the unique identifier
:key="tab.value" (ensure the tabs array items have a value property) — update
the button v-for (v-for="(tab, idx) in tabs") to use tab.value as the key and
keep existing bindings like selection logic that reference tab.value.

Comment on lines +39 to +42
<component
:is="tab.content"
v-if="tab.content"
/>
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

Forward contentProps to fallback content component.

Line 39–42 renders tab.content but drops tab.contentProps, so the documented/type-supported props contract is not honored.

Proposed fix
-        <component
-          :is="tab.content"
-          v-if="tab.content"
-        />
+        <component
+          v-if="tab.content"
+          :is="tab.content"
+          v-bind="tab.contentProps"
+        />
📝 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
<component
:is="tab.content"
v-if="tab.content"
/>
<component
v-if="tab.content"
:is="tab.content"
v-bind="tab.contentProps"
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/inspira/ui/animated-tabs/AnimatedTabsFadeInDiv.vue` around
lines 39 - 42, The fallback rendering of the dynamic component (the <component
:is="tab.content" v-if="tab.content" /> block in AnimatedTabsFadeInDiv.vue)
ignores tab.contentProps; update that component invocation to forward the props
by binding contentProps (e.g., :props="tab.contentProps" or
v-bind="tab.contentProps") so the dynamic/fallback content receives the
documented props; ensure the binding is applied only when tab.contentProps
exists and keep the v-if="tab.content" check.

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.

1 participant