Skip to content

feat(ui): Add Tabs component#207

Merged
kylengn merged 4 commits intomainfrom
feat/tabs
Dec 10, 2025
Merged

feat(ui): Add Tabs component#207
kylengn merged 4 commits intomainfrom
feat/tabs

Conversation

@kylengn
Copy link
Contributor

@kylengn kylengn commented Dec 9, 2025

Summary by CodeRabbit

  • New Features
    • Added a new Tabs component with support for uncontrolled and controlled states, animated indicators, and multiple tab configurations.
    • Enhanced Modal component with the ability to customize overlay styling via a new overlayClassName prop.
    • Added a new modal overlay color token to the design system for consistent styling across themes.

✏️ Tip: You can customize this high-level summary in your review settings.

@kylengn kylengn self-assigned this Dec 9, 2025
Copilot AI review requested due to automatic review settings December 9, 2025 15:19
@vercel
Copy link

vercel bot commented Dec 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
react-ui Ready Ready Preview Comment Dec 10, 2025 3:14am

@coderabbitai
Copy link

coderabbitai bot commented Dec 9, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The PR adds a comprehensive Tabs component with controlled/uncontrolled modes and animated indicators, introduces a modal overlay color token in the theme, adds an overlayClassName prop to ModalContent, and refactors several component index files to use wildcard re-exports for consistency.

Changes

Cohort / File(s) Summary
Color Token Addition
libs/react/ui/index.css
Added --background-modal-overlay and --color-background-modal-overlay CSS variables in both light and dark modes, resolving to var(--color-alpha-black-88).
Export Pattern Refactoring
libs/react/ui/src/components/badge/index.ts, libs/react/ui/src/components/dropdown-menu/index.ts, libs/react/ui/src/components/modal/index.ts, libs/react/ui/src/components/toast/index.ts
Replaced explicit named exports with wildcard re-exports (export * from) across four component index files.
Modal Enhancement
libs/react/ui/src/components/modal/modal.tsx, libs/react/ui/src/components/modal/modal.stories.tsx
Added optional overlayClassName?: string prop to ModalContentProps and applied it in two Storybook stories to demonstrate modal overlay styling via bg-background-modal-overlay class.
Tabs Component (New Feature)
libs/react/ui/src/components/tabs/tabs.tsx, libs/react/ui/src/components/tabs/index.ts, libs/react/ui/src/components/tabs/tabs.stories.tsx
Implemented comprehensive Tabs component with TabsProvider context, controlled/uncontrolled modes, animated indicator (framer-motion), trigger registration mechanism, and hook-based API. Includes three Storybook stories (Default, Controlled, MultipleTabs).
Public API Expansion
libs/react/ui/src/components/index.ts
Added exports for moving-border and tabs modules.

Sequence Diagram

sequenceDiagram
    participant Consumer as Consumer Component
    participant Tabs as Tabs Provider
    participant Context as TabsContext
    participant TabsList as TabsList
    participant Trigger as TabsTrigger
    participant Contents as TabsContents
    participant Content as TabsContent

    Consumer->>Tabs: Render Tabs with value & onValueChange
    activate Tabs
    Tabs->>Context: Initialize context (activeValue, resolvedValue)
    Tabs->>Trigger: Render first TabsTrigger

    activate Trigger
    Trigger->>Trigger: On Mount: registerTrigger(value)
    Trigger->>Context: Update trigger registry
    deactivate Trigger

    Consumer->>Trigger: Click TabsTrigger
    activate Trigger
    Trigger->>Context: setActiveValue(newValue)
    Trigger->>Context: Call onValueChange(newValue)
    deactivate Trigger

    Context->>TabsList: Trigger re-render
    TabsList->>TabsList: Update indicator position (requestAnimationFrame)
    
    Context->>Contents: Trigger re-render
    Contents->>Contents: Find matching TabsContent by value
    Contents->>Content: Render active TabsContent
    Content->>Consumer: Display active content

    deactivate Tabs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • libs/react/ui/src/components/tabs/tabs.tsx: Review the Tabs provider context logic, state management (controlled/uncontrolled modes), trigger registration mechanism, and indicator positioning calculations using framer-motion.
  • Export refactoring pattern: Verify wildcard re-exports are intentional and don't expose unintended internal symbols across badge, dropdown-menu, modal, and toast index files.
  • Modal overlay styling: Confirm the overlayClassName prop is properly threaded through ModalContent to ModalOverlay in both desktop and non-desktop paths.

Possibly related PRs

  • feat(ui): add Modal component #128: Modifies modal implementation and CSS variables (--color-background-modal-overlay and related theme tokens), directly overlapping with modal CSS and component changes.
  • feat(ui): Add Date Picker component #197: Modifies libs/react/ui/src/components/modal/modal.stories.tsx (ImportForm story), potentially conflicting with overlay styling changes introduced here.
  • feat(ui): add Badge component #119: Refactors badge component exports using the same wildcard re-export pattern applied across multiple files in this PR.

Suggested reviewers

  • EnzalRad
  • dvxam
  • noe-charmet

Poem

🐰 Whiskers twitching with delight,
New Tabs hop left and right—
With overlays in black and cream,
State management's a dream,
Exports wildcard, clean and bright!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main addition: a new Tabs component with comprehensive implementation, stories, and integration.

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.

@argos-ci
Copy link

argos-ci bot commented Dec 9, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) 👍 Changes approved 11 changed, 3 added Dec 10, 2025, 3:16 AM

Copy link

@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: 0

🧹 Nitpick comments (5)
libs/react/ui/src/components/tabs/tabs.tsx (3)

68-94: Minor duplication in initial tab selection logic.

The initial tab selection logic exists in both the useEffect (lines 68-79) and registerTrigger (lines 85-88). While the initialSet ref prevents conflicts, you could consider consolidating this to a single location for clarity. The current implementation works correctly though.


159-182: Consider ResizeObserver for more robust indicator updates.

The current implementation handles window resize but won't detect container-level size changes (e.g., sidebar toggle, parent flex changes). A ResizeObserver on listRef.current would be more robust. This is a nice-to-have enhancement for edge cases.

 useEffect(() => {
   const updateIndicator = () => {
     const activeTrigger = getTriggerElement(activeValue);

     if (activeTrigger && listRef.current) {
       const listRect = listRef.current.getBoundingClientRect();
       const triggerRect = activeTrigger.getBoundingClientRect();
       setIndicatorStyle({
         left: triggerRect.left - listRect.left,
         width: triggerRect.width,
       });
     }
   };

   const rafId = requestAnimationFrame(() => {
     updateIndicator();
   });

-  window.addEventListener('resize', updateIndicator);
+  const resizeObserver = new ResizeObserver(updateIndicator);
+  if (listRef.current) {
+    resizeObserver.observe(listRef.current);
+  }
   return () => {
     cancelAnimationFrame(rafId);
-    window.removeEventListener('resize', updateIndicator);
+    resizeObserver.disconnect();
   };
 }, [activeValue, getTriggerElement]);

230-247: Add accessibility attributes for better screen reader support.

The tab trigger is missing aria-selected and explicit type="button" attributes for proper accessibility and to prevent unintended form submissions.

     <motion.button
       ref={localRef}
       data-slot="tabs-trigger"
       role="tab"
+      type="button"
+      aria-selected={isActive}
       whileTap={{scale: 0.95}}
       onClick={() => handleValueChange(value)}
       data-state={isActive ? 'active' : 'inactive'}
libs/react/ui/src/components/tabs/tabs.stories.tsx (2)

17-29: Consider rendering TabsContents in the Default story.

The Default story only renders TabsList with triggers but omits TabsContents. While this demonstrates the tab switching UI, it's inconsistent with the Controlled and MultipleTabs stories which both include content panels. Consider adding TabsContents with corresponding TabsContent components to provide a complete example.

Apply this diff to add content panels:

 export const Default: Story = {
   args: {defaultValue: 'analytics'} as never,
   render: () => (
     <div className="bg-background-neutral-background p-24 w-[80vw]">
       <Tabs defaultValue="analytics">
         <TabsList className="gap-12 border-b border-neutral-strong">
           <TabsTrigger value="analytics">Analytics</TabsTrigger>
           <TabsTrigger value="jobs">Jobs</TabsTrigger>
         </TabsList>
+        <TabsContents>
+          <TabsContent value="analytics">
+            <div className="py-16">
+              <p className="text-foreground-neutral-base">Analytics content</p>
+            </div>
+          </TabsContent>
+          <TabsContent value="jobs">
+            <div className="py-16">
+              <p className="text-foreground-neutral-base">Jobs content</p>
+            </div>
+          </TabsContent>
+        </TabsContents>
       </Tabs>
     </div>
   ),
 };

18-18: Consider removing the as never type assertions.

The as never type assertions on the args property suggest a type mismatch between the Story args and the Tabs component props. These assertions bypass type safety and could hide legitimate type errors.

If the Storybook args are not meant to control the component directly (since each story uses its own render function), consider removing the args property entirely or investigate the root cause of the type mismatch.

Also applies to: 32-32, 65-65

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3fd53 and 2e43e3d.

📒 Files selected for processing (11)
  • libs/react/ui/index.css (3 hunks)
  • libs/react/ui/src/components/badge/index.ts (1 hunks)
  • libs/react/ui/src/components/dropdown-menu/index.ts (1 hunks)
  • libs/react/ui/src/components/index.ts (1 hunks)
  • libs/react/ui/src/components/modal/index.ts (1 hunks)
  • libs/react/ui/src/components/modal/modal.stories.tsx (2 hunks)
  • libs/react/ui/src/components/modal/modal.tsx (2 hunks)
  • libs/react/ui/src/components/tabs/index.ts (1 hunks)
  • libs/react/ui/src/components/tabs/tabs.stories.tsx (1 hunks)
  • libs/react/ui/src/components/tabs/tabs.tsx (1 hunks)
  • libs/react/ui/src/components/toast/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

⚙️ CodeRabbit configuration file

We handle errors at the edge of our applications in most cases. Do not recommend to add error handling around every single function. We prefer them to bubble up and be handled at upper layers.

Files:

  • libs/react/ui/src/components/index.ts
  • libs/react/ui/src/components/tabs/index.ts
  • libs/react/ui/src/components/modal/modal.tsx
  • libs/react/ui/src/components/tabs/tabs.stories.tsx
  • libs/react/ui/src/components/modal/modal.stories.tsx
  • libs/react/ui/src/components/badge/index.ts
  • libs/react/ui/src/components/tabs/tabs.tsx
  • libs/react/ui/src/components/modal/index.ts
  • libs/react/ui/index.css
  • libs/react/ui/src/components/toast/index.ts
  • libs/react/ui/src/components/dropdown-menu/index.ts
🧬 Code graph analysis (2)
libs/react/ui/src/components/tabs/tabs.stories.tsx (1)
libs/react/ui/src/components/tabs/tabs.tsx (5)
  • Tabs (302-302)
  • TabsList (303-303)
  • TabsTrigger (304-304)
  • TabsContents (305-305)
  • TabsContent (306-306)
libs/react/ui/src/components/modal/modal.stories.tsx (1)
libs/react/ui/src/components/modal/modal.tsx (1)
  • ModalContent (294-294)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: Continuous integration
🔇 Additional comments (14)
libs/react/ui/src/components/modal/modal.tsx (1)

128-128: LGTM! Clean addition of overlayClassName prop.

The prop is properly typed, destructured, and consistently passed to ModalOverlay in both mobile and desktop rendering paths.

Also applies to: 136-136, 144-144, 168-168

libs/react/ui/src/components/dropdown-menu/index.ts (1)

1-1: Consistent with the project's barrel export pattern.

Wildcard re-exports simplify maintenance. Ensure the source module only exports intended public API to avoid accidental exposure of internal symbols.

libs/react/ui/src/components/badge/index.ts (1)

1-4: LGTM!

Consistent refactor to wildcard re-exports across all badge submodules.

libs/react/ui/src/components/toast/index.ts (1)

1-2: LGTM!

Consistent with the project-wide barrel export refactor.

libs/react/ui/index.css (1)

196-196: LGTM! New modal overlay token added correctly.

The token follows the existing naming convention and is properly defined for both light/dark modes with the corresponding Tailwind theme alias. Using the same alpha-black-88 value in both themes is appropriate for modal overlays.

Also applies to: 402-402, 748-748

libs/react/ui/src/components/tabs/tabs.tsx (3)

257-279: LGTM!

The TabsContents component correctly filters children by matching the value prop against the active tab value.


286-299: LGTM with optional ARIA enhancement.

The component correctly renders conditionally based on active state. For complete WAI-ARIA Tabs pattern compliance, consider adding id and aria-labelledby attributes to link panels with their triggers. This is optional for basic functionality.


301-314: LGTM!

Clean and comprehensive export of all components, the hook, and associated types.

libs/react/ui/src/components/tabs/index.ts (1)

1-1: LGTM!

The barrel export follows the established pattern for component modules in this codebase, consistent with the refactoring applied to modal, toast, and other components.

libs/react/ui/src/components/index.ts (1)

20-22: LGTM!

The new exports for moving-border and tabs modules follow the established wildcard re-export pattern and maintain alphabetical ordering.

libs/react/ui/src/components/modal/index.ts (1)

1-1: LGTM!

The refactoring to wildcard re-exports simplifies the module structure and aligns with the pattern applied across other components (tabs, toast, badge, dropdown-menu) in this PR.

libs/react/ui/src/components/tabs/tabs.stories.tsx (2)

31-62: LGTM!

The Controlled story effectively demonstrates controlled usage with value and onValueChange props, displaying both tab triggers and content panels.


64-100: LGTM!

The MultipleTabs story provides a comprehensive example of uncontrolled tabs usage with multiple tab panels, demonstrating the full component API.

libs/react/ui/src/components/modal/modal.stories.tsx (1)

143-143: CSS token configuration is correct and follows established patterns.

The overlayClassName="bg-background-modal-overlay" prop properly uses the new overlay customization feature. The CSS variable --background-modal-overlay is defined in libs/react/ui/index.css and mapped through the theme token system (--color-background-modal-overlay). This follows the same pattern as other successfully-used background tokens like bg-background-neutral-base, bg-background-components-base, and bg-background-field-base throughout the codebase.

Copy link

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.

Pull request overview

This PR adds a new Tabs component to the React UI library with support for both controlled and uncontrolled states, animated tab indicators, and integrates several related changes across the codebase.

Key Changes:

  • New Tabs component with context-based state management, animated indicator, and framer-motion integration
  • Refactored export patterns across multiple components (toast, modal, dropdown-menu, badge) to use export * syntax
  • Enhanced Modal component with customizable overlay styling via overlayClassName prop
  • Added --background-modal-overlay theme token for consistent modal overlay styling

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
libs/react/ui/src/components/tabs/tabs.tsx New Tabs component implementation with context API, controlled/uncontrolled modes, and animated indicator
libs/react/ui/src/components/tabs/tabs.stories.tsx Storybook stories demonstrating default, controlled, and multiple tabs usage patterns
libs/react/ui/src/components/tabs/index.ts Export file for Tabs component following the export * pattern
libs/react/ui/src/components/toast/index.ts Refactored to use export * pattern instead of explicit named exports
libs/react/ui/src/components/modal/modal.tsx Added overlayClassName prop to ModalContent for customizable overlay styling
libs/react/ui/src/components/modal/modal.stories.tsx Updated stories to demonstrate new overlayClassName prop usage
libs/react/ui/src/components/modal/index.ts Refactored to use export * pattern
libs/react/ui/src/components/dropdown-menu/index.ts Refactored to use export * pattern
libs/react/ui/src/components/badge/index.ts Refactored to use export * pattern
libs/react/ui/src/components/index.ts Added exports for Tabs and MovingBorder components
libs/react/ui/index.css Added --background-modal-overlay theme variable for light and dark modes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dvxam
dvxam previously approved these changes Dec 9, 2025
@kylengn kylengn merged commit 45a8a61 into main Dec 10, 2025
5 checks passed
@kylengn kylengn deleted the feat/tabs branch December 10, 2025 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants