Skip to content

feat(Interaction): new Interaction component#617

Open
noahchoii wants to merge 27 commits into
mainfrom
interaction-component
Open

feat(Interaction): new Interaction component#617
noahchoii wants to merge 27 commits into
mainfrom
interaction-component

Conversation

@noahchoii

@noahchoii noahchoii commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Description of Changes

Summary by CodeRabbit

  • New Features
    • Added a shared Interaction wrapper to provide consistent hover, focus, active, and roving-highlight behaviors across interactive components.
  • Refactor
    • Updated buttons, checkboxes, menus, radio/switch, select, tabs, multi-select, segmented controls, and text inputs to use the shared interaction handling for more uniform interactive states.
    • Consolidated interaction styling so components share the same interaction visuals.
  • Tests
    • Refreshed Interaction tests to verify accessibility, ref/className behavior, and “form” rendering differences.
  • Documentation
    • Expanded Interaction Storybook coverage to better demonstrate interaction states.
  • Chores
    • Added a Storybook pseudo-states addon for more accurate previews.
  • Previously, we used the ::before pseudo-element to handle interaction states. However, this approach had limitations, as it consumed one of the available pseudo-element slots for users, and if a user absolutely had to use ::before, the interaction styles could break.
  • To resolve this, I added an Interaction component that inserts an actual div element inside the interactive element, replacing the previous method for handling interaction styles.
  • In addition, I recall product developers asking how to add interaction styles to their customs. By exporting this Interaction component, external users can now easily manage interaction styles when customizing components, allowing for a consistent interaction experience across the entire web application.
  • Another thing we need to look into is how interaction styles are being customized in the product code. Upon review, I found that about 2 to 3 files per product are currently using custom styles for this. Therefore, it might be a good idea to provide a migration CLI along with this if necessary.

Checklist

Before submitting the PR, please make sure you have checked all of the following items.

  • The PR title follows the Conventional Commits convention. (e.g., feat, fix, docs, style, refactor, test, chore)
  • I have added tests for my changes.
  • I have updated the Storybook or relevant documentation.
  • I have added a changeset for this change. (e.g., for any changes that affect users, such as component prop changes or new features).
  • I have performed a self-code review.
  • I have followed the project's coding conventions and component patterns.

@noahchoii noahchoii requested a review from MaxLee-dev as a code owner June 16, 2026 01:23
@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4e5d36c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
vapor-ui Ready Ready Preview, Comment Jun 16, 2026 7:37am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@noahchoii, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 253337c0-a8e1-4441-a350-055254d0c231

📥 Commits

Reviewing files that changed from the base of the PR and between 9ce3d9a and 4e5d36c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (1)
  • apps/storybook/package.json
📝 Walkthrough

Walkthrough

Introduces a new Interaction React component with a CSS recipe (overlay, root, scale/type variants), then migrates 15 existing components (button, checkbox, switch, radio, radio-card, segmented-control, navigation-menu, pagination, tabs, text-input, textarea, menu, select, multi-select) from a static interaction() CSS mixin to wrapping their underlying elements with the new <Interaction> component. Adds Storybook pseudo-states addon support.

Changes

Interaction Component Extraction and Migration

Layer / File(s) Summary
Interaction CSS contract and public exports
packages/core/src/components/interaction/interaction.css.ts, packages/core/src/components/interaction/index.ts, packages/core/src/index.ts
Defines global CSS variables, the overlay style, and the root recipe with scale/type variant axes. Exports InteractionVariants type and wires up barrel re-exports.
Interaction component implementation
packages/core/src/components/interaction/interaction.tsx
Splits variant props, validates single-child requirement, creates a presentational overlay via useRenderElement, and clones the child to inject root classes and overlay content conditionally based on type. Exports Interaction namespace with State and Props types.
Interaction tests and Storybook stories
packages/core/src/components/interaction/interaction.test.tsx, packages/core/src/components/interaction/interaction.stories.tsx
Comprehensive test suite covering child rendering, className routing, variant prop flow, error throwing, overlay injection per type, ref preservation, and axe accessibility. Storybook stories include Default and TestBed with pseudo-state configuration for hover/focus/active selectors across scale and type variants.
Migrate default/form interaction components
packages/core/src/components/{button,checkbox,switch,radio,radio-card,segmented-control,navigation-menu,pagination,tabs,text-input,textarea}/*
For 11 components: removes interaction() or interaction({ type: 'form' }) from each .css.ts recipe base styles, and adds an <Interaction> or <Interaction type="form"> wrapper in the corresponding .tsx render output.
Migrate roving interaction components
packages/core/src/components/{menu,select,multi-select}/*
For 3 components: removes interaction({ type: 'roving' }) from .css.ts recipe base styles, and wraps item/trigger elements with <Interaction type="roving"> in .tsx files.
Storybook pseudo-states addon setup
apps/storybook/.storybook/main.ts, apps/storybook/package.json
Adds storybook-addon-pseudo-states as a dev dependency and configures it in Storybook's main addon array to enable pseudo-state selector testing in stories.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • goorm-dev/vapor-ui#569: Introduces the SegmentedControl component whose item rendering is directly modified in this PR to replace the interaction() mixin with the new <Interaction> wrapper around BaseRadio.Root.
  • goorm-dev/vapor-ui#521: Refactors the underlying interaction mixin implementation in packages/core/src/styles/mixins/interactions.css.ts, which this PR removes entirely in favor of component-based <Interaction> wrappers.
  • goorm-dev/vapor-ui#515: Updates interaction/checkbox/radio CSS interaction-layer styling that overlaps with the CSS recipe and component files modified in this PR to replace mixin-based interaction styling.

Suggested labels

scope: all

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: introducing a new Interaction component. It reflects the primary objective of the pull request.
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
  • Commit unit tests in branch interaction-component

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.

@noahchoii noahchoii changed the title feat: new Interaction component feat(Interaction): new Interaction component Jun 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/components/checkbox/checkbox.tsx`:
- Around line 63-64: The CheckboxProvider component on line 63 of checkbox.tsx
is missing the invalid prop in its value object, preventing
CheckboxIndicatorPrimitive from accessing the invalid state needed for proper
styling and data attributes. Add the invalid prop to the value object passed to
CheckboxProvider alongside the existing size and indeterminate props to ensure
the invalid state is properly propagated through the context.

In `@packages/core/src/components/interaction/interaction.test.tsx`:
- Around line 95-107: The console.error mocking in the test may not be properly
restored if the assertion fails, causing the mock to leak into subsequent tests.
Wrap the code block containing the console.error mocking assignment and the
expect().toThrow() call in a try-finally statement to guarantee that
console.error is restored to its previousError value regardless of assertion
outcome. This same pattern needs to be applied at both affected locations (lines
95-107 and lines 110-118) to ensure consistent cleanup in all test cases.

In `@packages/core/src/components/interaction/interaction.tsx`:
- Around line 40-50: The children merge logic in the cloneElement call has two
issues: first, the order is incorrect with the overlay (element) appearing first
instead of last, which contradicts the test contract in interaction.test.tsx
Line 121; second, when variantProps.type is 'form', setting children to
undefined removes the original child.props.children instead of preserving them.
Fix this by reordering the children fragment to have child.props.children first
followed by element, and change the conditional to always include the merged
children regardless of type, ensuring the overlay consistently appears as the
last child.

In `@packages/core/src/components/navigation-menu/navigation-menu.tsx`:
- Around line 128-139: The Interaction wrapper migration is inconsistently
applied across components, causing inconsistent interaction state handling. At
packages/core/src/components/navigation-menu/navigation-menu.tsx, wrap the
NavigationMenuTriggerPrimitive component with the Interaction wrapper (similar
to how NavigationMenuLink is wrapped at the anchor location) to ensure
consistent state representation. Additionally, at
packages/core/src/components/pagination/pagination.tsx, apply the same
Interaction wrapping strategy to both PaginationPreviousPrimitive and
PaginationNextPrimitive components (which share the same styles.button axis as
PaginationButtonPrimitive) to maintain consistent state handling across all
pagination button variants.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 52a65614-385a-41d0-9755-909281737125

📥 Commits

Reviewing files that changed from the base of the PR and between b2fddfa and 660e4f9.

⛔ Files ignored due to path filters (4)
  • packages/core/__tests__/screenshots/interaction--test-bed-1-chrome-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-edge-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-firefox-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-safari-darwin-.png is excluded by !**/*.png
📒 Files selected for processing (34)
  • packages/core/src/components/button/button.css.ts
  • packages/core/src/components/button/button.tsx
  • packages/core/src/components/checkbox/checkbox.css.ts
  • packages/core/src/components/checkbox/checkbox.tsx
  • packages/core/src/components/interaction/index.ts
  • packages/core/src/components/interaction/interaction.css.ts
  • packages/core/src/components/interaction/interaction.stories.tsx
  • packages/core/src/components/interaction/interaction.test.tsx
  • packages/core/src/components/interaction/interaction.tsx
  • packages/core/src/components/menu/menu.css.ts
  • packages/core/src/components/menu/menu.tsx
  • packages/core/src/components/multi-select/multi-select.css.ts
  • packages/core/src/components/multi-select/multi-select.tsx
  • packages/core/src/components/navigation-menu/navigation-menu.css.ts
  • packages/core/src/components/navigation-menu/navigation-menu.tsx
  • packages/core/src/components/pagination/pagination.css.ts
  • packages/core/src/components/pagination/pagination.tsx
  • packages/core/src/components/radio-card/radio-card.css.ts
  • packages/core/src/components/radio-card/radio-card.tsx
  • packages/core/src/components/radio/radio.css.ts
  • packages/core/src/components/radio/radio.tsx
  • packages/core/src/components/segmented-control/segmented-control.css.ts
  • packages/core/src/components/segmented-control/segmented-control.tsx
  • packages/core/src/components/select/select.css.ts
  • packages/core/src/components/select/select.tsx
  • packages/core/src/components/switch/switch.css.ts
  • packages/core/src/components/switch/switch.tsx
  • packages/core/src/components/tabs/tabs.css.ts
  • packages/core/src/components/tabs/tabs.tsx
  • packages/core/src/components/text-input/text-input.css.ts
  • packages/core/src/components/text-input/text-input.tsx
  • packages/core/src/components/textarea/textarea.css.ts
  • packages/core/src/components/textarea/textarea.tsx
  • packages/core/src/index.ts
💤 Files with no reviewable changes (13)
  • packages/core/src/components/text-input/text-input.css.ts
  • packages/core/src/components/switch/switch.css.ts
  • packages/core/src/components/tabs/tabs.css.ts
  • packages/core/src/components/button/button.css.ts
  • packages/core/src/components/checkbox/checkbox.css.ts
  • packages/core/src/components/textarea/textarea.css.ts
  • packages/core/src/components/radio/radio.css.ts
  • packages/core/src/components/segmented-control/segmented-control.css.ts
  • packages/core/src/components/radio-card/radio-card.css.ts
  • packages/core/src/components/select/select.css.ts
  • packages/core/src/components/menu/menu.css.ts
  • packages/core/src/components/navigation-menu/navigation-menu.css.ts
  • packages/core/src/components/multi-select/multi-select.css.ts

Comment thread packages/core/src/components/checkbox/checkbox.tsx
Comment thread packages/core/src/components/interaction/interaction.test.tsx Outdated
Comment thread packages/core/src/components/interaction/interaction.tsx Outdated
Comment thread packages/core/src/components/navigation-menu/navigation-menu.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/core/src/components/interaction/interaction.test.tsx (1)

66-67: 💤 Low value

중복 검증을 제거하여 가독성을 개선하세요.

queryByTestIdnull을 반환하면 해당 요소는 자동으로 document에 존재하지 않습니다. Line 67의 not.toBeInTheDocument() 검증만으로 충분합니다.

♻️ 제안하는 수정
-        expect(interactionElement).toBeNull();
         expect(interactionElement).not.toBeInTheDocument();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/interaction/interaction.test.tsx` around lines
66 - 67, The code contains redundant assertions checking the same condition
twice. The expect(interactionElement).toBeNull() assertion on the first line is
redundant because when queryByTestId returns null, the subsequent
expect(interactionElement).not.toBeInTheDocument() assertion already verifies
this condition. Remove the redundant toBeNull() assertion and keep only the
not.toBeInTheDocument() check to improve code readability and avoid unnecessary
duplicate validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/components/interaction/interaction.test.tsx`:
- Around line 66-67: The code contains redundant assertions checking the same
condition twice. The expect(interactionElement).toBeNull() assertion on the
first line is redundant because when queryByTestId returns null, the subsequent
expect(interactionElement).not.toBeInTheDocument() assertion already verifies
this condition. Remove the redundant toBeNull() assertion and keep only the
not.toBeInTheDocument() check to improve code readability and avoid unnecessary
duplicate validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 53b7ceae-ea4d-4646-8f52-09c3b4d04040

📥 Commits

Reviewing files that changed from the base of the PR and between 660e4f9 and 2930624.

⛔ Files ignored due to path filters (5)
  • packages/core/__tests__/screenshots/interaction--test-bed-1-chrome-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-edge-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-firefox-darwin-.png is excluded by !**/*.png
  • packages/core/__tests__/screenshots/interaction--test-bed-1-safari-darwin-.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/storybook/.storybook/main.ts
  • apps/storybook/package.json
  • packages/core/src/components/interaction/interaction.stories.tsx
  • packages/core/src/components/interaction/interaction.test.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/storybook/.storybook/main.ts

@vapor-ui

Copy link
Copy Markdown
Collaborator

All tests passed!

Tests Passed Failed Duration Report
160 160 0 4m 27s Open report ↗︎

Click here if you need to update snapshots.

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