Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

docs: improve component explorer UI and apply design tokens#471

Draft
MaxLee-dev wants to merge 39 commits into
main from
docs-component-analyzer
Draft

docs: improve component explorer UI and apply design tokens #471
MaxLee-dev wants to merge 39 commits into
main from
docs-component-analyzer

Conversation

@MaxLee-dev

@MaxLee-dev MaxLee-dev commented Feb 6, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown
Contributor

Description of Changes

The UI of the Component Explorer has been improved and design tokens applied:

  • ComponentExplorer and related UI components implementation (Component StructurePanel, HighlightOverlay, PartButton)
  • UI improvements and design token application
image

Checklist

  • The PR title follows the Conventional Commits convention.
  • I have added tests for my changes.
  • I have updated the Storybook or relevant documentation.
  • I have added a changeset for this change.
  • I have performed a self-code review.
  • I have followed the project's coding conventions and component patterns.

Summary by CodeRabbit

  • New Features

    • Interactive Component Explorer: two‐pane anatomy list with keyboard navigation and selectable parts.
    • Visual highlight overlay in previews that labels and animates selected parts.
    • Embedded preview iframe with retryable loading and an "explorer" preview mode toggle.
  • Cross-window Communication

    • Explorer and preview exchange readiness, available-parts, and highlight/clear commands for live interaction.
  • Documentation

    • Added "Component Structure" section to the Dialog docs with an embedded explorer demo.

MaxLee-dev and others added 6 commits February 5, 2026 16:51
...ponent explorer
- Created anatomy JSON files for Popover, RadioGroup, Radio, Select, Sheet, Switch, Table, Tabs, Toast, Tooltip components.
- Implemented a script to generate anatomy files from component parts.
- Enhanced the preview page to support an explorer mode for visualizing component anatomy.
- Developed the ComponentExplorer and AnatomyPanel components for displaying component parts.
- Added highlight overlay functionality to visually indicate hovered parts in the component preview.
- Updated the layout and styling of the preview components for better user experience.
- Remove 'Live Preview' header from component explorer for cleaner UI
- Replace hardcoded colors with Vapor UI design tokens in highlight overlay
- Remove chevron icon from part button for simplified design
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

changeset-bot Bot commented Feb 6, 2026
edited
Loading

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0421f12

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 Bot commented Feb 6, 2026
edited
Loading

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 Mar 25, 2026 6:00am

Request Review

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @MaxLee-dev, I'm Gemini Code Assist1 ! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new interactive feature to the Vapor UI documentation: a 'Component Explorer'. This tool allows users to delve into the internal structure of UI components, providing a visual representation of their individual parts and how they are composed. The implementation includes a new automated process for generating component anatomy data, a dedicated UI for the explorer, and a robust communication mechanism between the main documentation page and the component preview iframe to enable interactive highlighting.

Highlights

  • New Component Explorer Feature: Introduced an interactive 'Component Explorer' that allows users to visualize and highlight the anatomical parts of UI components directly within the documentation. This enhances understanding of component structure.
  • Automated Anatomy Data Generation: Implemented a new build script (generate-anatomy.mjs) that automatically parses component source files to create structured JSON data describing each component's parts. This data is then used by the Component Explorer.
  • Design Token Integration: Replaced hardcoded color values (blue-500, white) in the highlight overlay with Vapor UI design tokens (v-blue-500, v-white), ensuring consistency with the project's design system.
  • UI Refinements: Streamlined the Component Explorer's user interface by removing an unnecessary 'Live Preview' header and simplifying the PartButton component by removing the ChevronRightOutlineIcon.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/website/content/docs/components/dialog.mdx
    • Added a new 'Anatomy' section for the Dialog component, integrating the new ComponentExplorer to showcase its structure.
  • apps/website/package.json
    • Added a new script generate:anatomy to automate the creation of component anatomy JSON data.
  • apps/website/public/components/anatomy/*.json
    • Added numerous new JSON files (e.g., avatar.json, breadcrumb.json, dialog.json, menu.json, select.json) that define the anatomical parts for various UI components. These files are generated by the new anatomy script.
  • apps/website/scripts/generate-anatomy.mjs
    • New script responsible for parsing component index.parts.ts files and generating corresponding JSON anatomy data, which is crucial for the Component Explorer.
  • apps/website/src/app/preview/[component]/page.tsx
    • Modified to accept an explorer search parameter, enabling a specialized rendering mode for the component preview page when accessed via the Component Explorer.
  • apps/website/src/app/preview/[component]/preview-wrapper.tsx
    • Updated to conditionally render the HighlightOverlay when in explorer mode and to ensure the dynamic component is centered within the preview iframe.
  • apps/website/src/app/preview/layout.tsx
    • Adjusted the html and body element styles to ensure full height and proper centering of content within the preview iframe, optimizing the display for the Component Explorer.
  • apps/website/src/components/component-explorer/anatomy-panel.tsx
    • New component responsible for displaying the list of component parts, filtering primitives, and managing hover interactions to trigger highlighting in the preview iframe.
  • apps/website/src/components/component-explorer/component-explorer.tsx
    • New core component that orchestrates the Component Explorer. It fetches anatomy data, renders the component preview in an iframe, and manages communication for interactive highlighting.
  • apps/website/src/components/component-explorer/iframe/highlight-overlay.tsx
    • New component rendered inside the component preview iframe. It creates a visual overlay to highlight specific component parts based on messages received from the parent window, now using Vapor UI design tokens for colors.
  • apps/website/src/components/component-explorer/iframe/use-highlight-receiver.ts
    • New custom React hook to facilitate receiving highlight messages from the parent window within the iframe context.
  • apps/website/src/components/component-explorer/index.ts
    • New entry point file for the Component Explorer module, exporting its main components and types.
  • apps/website/src/components/component-explorer/part-button.tsx
    • New component for interactive buttons representing individual component parts, with the ChevronRightOutlineIcon removed to simplify the interface.
  • apps/website/src/components/component-explorer/types.ts
    • New file defining TypeScript types and message constants for the internal communication within the Component Explorer system.
  • apps/website/src/components/component-explorer/use-explorer-communication.ts
    • New custom React hook to manage sending highlight messages from the parent window to the component preview iframe.
  • apps/website/src/components/demo/examples/dialog/anatomy-dialog.tsx
    • New example component for the Dialog, specifically structured with data-part attributes to be compatible with the Component Explorer's highlighting functionality.
  • apps/website/src/mdx-components.tsx
    • Integrated the new ComponentExplorer component, making it available for use within MDX documentation files.
Activity
  • MaxLee-dev created this pull request to refactor and improve the component explorer UI and apply design tokens.
  • The pull request includes a new script to generate component anatomy data, new components for the explorer UI, and updates to existing preview infrastructure.
  • MaxLee-dev has performed a self-code review and confirmed adherence to project coding conventions and component patterns.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

이 PR은 문서 사이트에 컴포넌트의 구조를 시각적으로 탐색할 수 있는 'Component Explorer' 기능을 추가하는 중요한 리팩토링입니다. 전반적으로 코드 품질이 높고, 새로운 기능이 잘 구현되었습니다. 특히 postMessage를 이용한 iframe 통신과 하이라이트 로직이 인상적입니다. 몇 가지 UI 및 성능 개선을 위한 제안 사항을 코드에 남겼습니다. 이 멋진 기능 추가에 감사드립니다!

Comment thread apps/website/src/components/component-explorer/component-explorer.tsx Outdated
Comment on lines +62 to +75
useEffect(() => {
findAndHighlight();

if (!highlightedPart) return;

// Re-calculate position on scroll/resize
window.addEventListener('scroll', findAndHighlight, true);
window.addEventListener('resize', findAndHighlight);

return () => {
window.removeEventListener('scroll', findAndHighlight, true);
window.removeEventListener('resize', findAndHighlight);
};
}, [highlightedPart, findAndHighlight]);

@gemini-code-assist gemini-code-assist Bot Feb 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

scrollresize 이벤트에 findAndHighlight 함수를 직접 연결하면 이벤트가 발생할 때마다 getBoundingClientRect가 호출되어 성능 저하를 유발할 수 있습니다. 특히 스크롤 시 잦은 reflow는 UI 버벅임(jank)의 원인이 될 수 있습니다. lodash-es가 이미 의존성에 있으므로 throttle 함수를 사용해 이벤트 핸들러의 호출 빈도를 조절하여 성능을 개선하는 것을 권장합니다.

// 예시 (lodash-es/throttle 사용)
import { throttle } from 'lodash-es';
// ...
useEffect(() => {
 findAndHighlight();
 if (!highlightedPart) return;
 const throttledFindAndHighlight = throttle(findAndHighlight, 100);
 // Re-calculate position on scroll/resize
 window.addEventListener('scroll', throttledFindAndHighlight, true);
 window.addEventListener('resize', throttledFindAndHighlight);
 return () => {
 window.removeEventListener('scroll', throttledFindAndHighlight, true);
 window.removeEventListener('resize', throttledFindAndHighlight);
 };
}, [highlightedPart, findAndHighlight]);

MaxLee-dev reacted with thumbs up emoji

@noahchoii noahchoii Feb 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can also use ResizeObserver.

MaxLee-dev reacted with thumbs up emoji

@MaxLee-dev MaxLee-dev Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ResizeObserver only detects changes to the element's size. However, when used in a menu or non-modal form, scrolling may occur. In such cases, as ResizeObserver cannot detect position changes, we implemented it by adding scroll and resize event listeners.

@noahchoii noahchoii Feb 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since scroll and resize are separate events, comparing ResizeObserver and scroll doesn't seem particularly meaningful. We're still adding separate event handlers for resize and scroll!

I mentioned ResizeObserver because I wondered if we could at least improve things for resize!

MaxLee-dev reacted with thumbs up emoji

@MaxLee-dev MaxLee-dev Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I was mixing scroll and resize concerns in the previous explanation.

I've updated this so scroll is removed, and resize-driven recalculation now uses
ResizeObserver instead of the window resize event. I also added MutationObserver
because OverlayPrimitive mount/unmount can invalidate the Trigger highlight without any
resize.

noahchoii reacted with thumbs up emoji
Comment thread apps/website/src/components/component-explorer/iframe/highlight-overlay.tsx Outdated
- Add AbortController to prevent race conditions when componentName changes rapidly
- Add error state management and display error UI with icon and message
- Add retry button for failed anatomy data fetches
- Check HTTP response status and handle network errors properly
- Fix highlight overlay z-index from z-1 to z-10
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comment thread apps/website/src/components/component-explorer/iframe/highlight-overlay.tsx Outdated
Comment on lines +62 to +75
useEffect(() => {
findAndHighlight();

if (!highlightedPart) return;

// Re-calculate position on scroll/resize
window.addEventListener('scroll', findAndHighlight, true);
window.addEventListener('resize', findAndHighlight);

return () => {
window.removeEventListener('scroll', findAndHighlight, true);
window.removeEventListener('resize', findAndHighlight);
};
}, [highlightedPart, findAndHighlight]);

@noahchoii noahchoii Feb 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can also use ResizeObserver.

MaxLee-dev reacted with thumbs up emoji
onBlur?: () => void;
}

export const PartButton = memo(function PartButton({

@noahchoii noahchoii Feb 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand that using memo to memoize a component prevents the component itself from re-rendering. Is this part okay?

@MaxLee-dev MaxLee-dev Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After revisiting this, I decided that memo at the PartButton level was not the right
optimization point, so I removed it.

Instead, I memoized AnatomyPanel so that parent re-renders caused by hover state changes,
which only affect the preview highlight, do not propagate through the entire panel subtree.

This works because the props passed to AnatomyPanel are stable in the current structure:

  • panelProps is wrapped with useMemo
  • the handlers are kept stable with useCallback
  • availableParts only changes reference when its actual contents change

As a result, hover-only updates do not re-render AnatomyPanel, and its child PartButtons
are skipped together.
When values that actually affect the panel UI, such as selectedPart or availableParts,
change, a re-render is still intentional.

onBlur,
}: PartButtonProps) {
return (
<Button

@noahchoii noahchoii Feb 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It might be worth considering whether this element should actually be a button. Currently, it directly handles all mouse events, and clicking it doesn't trigger any action.

Also, simply hovering the mouse sets aria-pressed to true, which seems to be an incorrect WAI-ARIA pattern!

@MaxLee-dev MaxLee-dev Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great catch! Both issues have been addressed.

Changes:

  • Replaced Button / RadioCard with div[role="option"] inside a div[role="listbox"] pattern
  • Removed aria-pressed entirely — selection state is now expressed via aria-selected
  • Added click-to-toggle selection behavior
  • Implemented keyboard navigation (Arrow keys, Enter/Space, Escape)
  • Both hover and focus-visible now provide the same highlight feedback

Since this component's interaction is "browse + select" rather than "form input", the WAI-ARIA Listbox pattern is a better semantic fit.

@MaxLee-dev MaxLee-dev changed the title (削除) refactor: improve component explorer UI and apply design tokens (削除ここまで) (追記) docs: improve component explorer UI and apply design tokens (追記ここまで) Feb 12, 2026

const ZERO_RECT: OverlayRect = { top: 0, left: 0, width: 0, height: 0 };

export function useHighlightTracking() {

@MaxLee-dev MaxLee-dev Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

• useHighlightTracking is a hook that retrieves the part currently to be highlighted within the iframe and continuously calculates the position and visibility of that element
to ensure the overlay is drawn in the correct location.

.join('');
}

export function useComponentExplorerController({

@MaxLee-dev MaxLee-dev Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

useComponentExplorerController is a hook that bundles the part selection state and iframe preview state from the parent component,
coordinating the entire behaviour of the component explorer, including communication with the iframe.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

coderabbitai[bot] reacted with eyes emoji

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
apps/website/src/components/component-explorer/iframe/use-highlight-tracking.ts (1)

83-95: 스크롤 이벤트 리스너 추가를 권장합니다.

getBoundingClientRect()는 뷰포트 기준 좌표를 반환합니다. iframe 내부 콘텐츠가 스크롤 가능한 경우, 스크롤 시 요소 위치가 변경되지만 현재 구현에서는 mutation/resize 이벤트가 발생할 때까지 오버레이 위치가 업데이트되지 않습니다.

♻️ 스크롤 리스너 추가 제안
 const mutationObserver = new MutationObserver(() => {
 scheduleFindAndHighlight();
 });
 resizeObserver.observe(document.documentElement);
 mutationObserver.observe(document.body, {
 childList: true,
 subtree: true,
 });
+ const handleScroll = () => {
+ scheduleFindAndHighlight();
+ };
+
+ window.addEventListener('scroll', handleScroll, true);
+
 return () => {
 if (frameRef.current !== null) {
 window.cancelAnimationFrame(frameRef.current);
 frameRef.current = null;
 }
 resizeObserver.disconnect();
 mutationObserver.disconnect();
+ window.removeEventListener('scroll', handleScroll, true);
 };

{ capture: true } 옵션을 사용하면 내부 스크롤 컨테이너의 스크롤 이벤트도 감지할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/website/src/components/component-explorer/iframe/use-highlight-tracking.ts`
around lines 83 - 95, The overlay isn't updated on scroll because only
ResizeObserver and MutationObserver are used; modify use-highlight-tracking to
also add a scroll event listener that calls scheduleFindAndHighlight (use
document.addEventListener('scroll', scheduleFindAndHighlight, { capture: true,
passive: true }) and optionally window.addEventListener('scroll', ...') for
safety) and ensure you remove the listener in the same cleanup path where
resizeObserver.disconnect() and mutationObserver.disconnect() are called;
reference the symbols scheduleFindAndHighlight, resizeObserver,
mutationObserver, and the hook use-highlight-tracking.ts when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/website/src/components/component-explorer/anatomy-panel.tsx`:
- Around line 50-52: The per-item mouseLeave sending onPartHover(null) causes
flicker when moving between items; stop calling onPartHover(null) from
item-level handlers (e.g., remove usage of handleMouseLeave in option elements)
and instead attach a single onMouseLeave on the list/container element to call
handleMouseLeave/onPartHover(null); keep per-item
onMouseEnter/handlePartHover(partName) as-is. Also apply the same change to the
other similar handlers referenced (the other item-level onMouseLeave
occurrences), so only the container handles clearing hover.
In `@apps/website/src/components/component-explorer/explorer-preview-pane.tsx`:
- Around line 61-68: The iframe's onError is unreliable so update the preview
load/error signaling to use an explicit postMessage handshake: modify the iframe
content (Preview) to postMessage({ type: 'preview-ready' }) on successful
initialization and postMessage({ type: 'preview-error', reason }) on failures,
then in this component subscribe to window.addEventListener('message') (paired
with iframeRef/current.origin checks) to resolve/clear the loading overlay and
call the existing handlers (handleIframeLoad / handleIframeError) based on the
received message types; keep iframeSrc, iframeTitle and iframeRef as-is but
remove reliance on onError and use the message-based ready/error protocol with
origin validation and a timeout fallback to avoid hanging overlays.
In
`@apps/website/src/components/component-explorer/use-component-explorer-controller.ts`:
- Around line 53-59: 현재 iframe이 리로드된 뒤(iframeLoaded가 true가 되는 시점)에 사이드바의
선택(activePart)과 프리뷰 하이라이트가 동기화되지 않아, preview가 빈 하이라이트를 보입니다; 해결하려면 useEffect 관련
로직을 수정하여 iframeSrc 변경시 resetAvailableParts 호출 외에 iframeLoaded가 true가 되면 현재
activePart를 다시 highlightPart로 보내도록 하세요—즉 previewPaneProps.iframeSrc 변화 및/또는
iframeLoaded 변화를 관찰하는 useEffect(또는 별도의 useEffect)를 추가/변경하여
highlightPart(activePart)를 호출하고 필요한 경우 activePart가 null이 아닐 때만 호출하도록 보장하세요 (참조:
previewPaneProps.iframeSrc, iframeLoaded, activePart, highlightPart,
resetAvailableParts).
---
Nitpick comments:
In
`@apps/website/src/components/component-explorer/iframe/use-highlight-tracking.ts`:
- Around line 83-95: The overlay isn't updated on scroll because only
ResizeObserver and MutationObserver are used; modify use-highlight-tracking to
also add a scroll event listener that calls scheduleFindAndHighlight (use
document.addEventListener('scroll', scheduleFindAndHighlight, { capture: true,
passive: true }) and optionally window.addEventListener('scroll', ...') for
safety) and ensure you remove the listener in the same cleanup path where
resizeObserver.disconnect() and mutationObserver.disconnect() are called;
reference the symbols scheduleFindAndHighlight, resizeObserver,
mutationObserver, and the hook use-highlight-tracking.ts when making the change.

i️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fb0ae3c1-9553-4872-83b1-09127920c698

📥 Commits

Reviewing files that changed from the base of the PR and between caf69a3 and 72731de.

📒 Files selected for processing (11)
  • apps/website/src/app/preview/[component]/preview-wrapper.tsx
  • apps/website/src/components/component-explorer/anatomy-panel.tsx
  • apps/website/src/components/component-explorer/component-explorer.tsx
  • apps/website/src/components/component-explorer/explorer-preview-pane.tsx
  • apps/website/src/components/component-explorer/iframe/highlight-overlay.tsx
  • apps/website/src/components/component-explorer/iframe/use-available-parts-reporter.ts
  • apps/website/src/components/component-explorer/iframe/use-highlight-tracking.ts
  • apps/website/src/components/component-explorer/part-option.tsx
  • apps/website/src/components/component-explorer/use-component-explorer-controller.ts
  • apps/website/src/components/component-explorer/use-part-selection.ts
  • apps/website/src/components/component-explorer/use-preview-iframe.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/website/src/components/component-explorer/iframe/highlight-overlay.tsx
  • apps/website/src/app/preview/[component]/preview-wrapper.tsx
  • apps/website/src/components/component-explorer/component-explorer.tsx

Comment thread apps/website/src/components/component-explorer/anatomy-panel.tsx
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

coderabbitai[bot] reacted with eyes emoji

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
apps/website/src/components/component-explorer/anatomy-panel.tsx (1)

141-149: tabIndex 계산식 중복은 헬퍼로 묶어두는 편이 안전합니다.

동일한 삼항식이 두 군데 반복되어 추후 수정 시 드리프트가 생기기 쉽습니다.

가벼운 리팩터 예시
@@
 const initialFocusablePart = filteredParts[0] ?? null;
+ const getOptionTabIndex = useCallback(
+ (partName: string) => {
+ if (selectedPart) {
+ return selectedPart === partName ? 0 : -1;
+ }
+ return initialFocusablePart === partName ? 0 : -1;
+ },
+ [selectedPart, initialFocusablePart],
+ );
@@
- tabIndex={
- selectedPart
- ? selectedPart === partName
- ? 0
- : -1
- : initialFocusablePart === partName
- ? 0
- : -1
- }
+ tabIndex={getOptionTabIndex(partName)}
@@
- tabIndex={
- selectedPart
- ? selectedPart === partName
- ? 0
- : -1
- : initialFocusablePart === partName
- ? 0
- : -1
- }
+ tabIndex={getOptionTabIndex(partName)}

Also applies to: 176-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/website/src/components/component-explorer/anatomy-panel.tsx` around
lines 141 - 149, The tabIndex ternary expression in the JSX is duplicated (once
around the selectedPart block and again at lines ~176-184); extract that logic
into a small helper (e.g., computeTabIndex or getTabIndex) that accepts the
current partName, selectedPart, and initialFocusablePart and returns 0 or -1,
then replace both inline ternaries with calls to that helper (update usages in
the component where tabIndex is set). This centralizes the logic, avoids drift,
and makes future changes safer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@apps/website/src/components/component-explorer/anatomy-panel.tsx`:
- Around line 141-149: The tabIndex ternary expression in the JSX is duplicated
(once around the selectedPart block and again at lines ~176-184); extract that
logic into a small helper (e.g., computeTabIndex or getTabIndex) that accepts
the current partName, selectedPart, and initialFocusablePart and returns 0 or
-1, then replace both inline ternaries with calls to that helper (update usages
in the component where tabIndex is set). This centralizes the logic, avoids
drift, and makes future changes safer.

i️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa4c15b9-9253-4c5b-9153-786ddb5836c5

📥 Commits

Reviewing files that changed from the base of the PR and between 72731de and a793d47.

📒 Files selected for processing (7)
  • apps/website/src/app/preview/[component]/preview-wrapper.tsx
  • apps/website/src/components/component-explorer/anatomy-panel.tsx
  • apps/website/src/components/component-explorer/explorer-preview-pane.tsx
  • apps/website/src/components/component-explorer/part-option.tsx
  • apps/website/src/components/component-explorer/types.ts
  • apps/website/src/components/component-explorer/use-component-explorer-controller.ts
  • apps/website/src/components/component-explorer/use-preview-iframe.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/website/src/components/component-explorer/explorer-preview-pane.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/website/src/components/component-explorer/use-component-explorer-controller.ts
  • apps/website/src/components/component-explorer/use-preview-iframe.ts
  • apps/website/src/components/component-explorer/part-option.tsx
  • apps/website/src/components/component-explorer/types.ts
  • apps/website/src/app/preview/[component]/preview-wrapper.tsx

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

coderabbitai[bot] reacted with eyes emoji

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/website/src/components/component-explorer/anatomy-panel.tsx`:
- Around line 54-59: The focusOption function uses
querySelector(`#part-${partName}`) which breaks when partName contains dots
(e.g., "Dialog.Trigger"); update the selector to escape the partName before
building the id selector (or use a safe attribute lookup) so querySelector
receives a valid CSS selector—e.g., use CSS.escape on the partName when
constructing the selector referenced in focusOption (and still check element
instanceof HTMLElement) so keyboard navigation works for compound part names;
target listRef and the querySelector usage in focusOption to apply the change.

i️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0289a10-29a2-44fd-9873-708931ab771b

📥 Commits

Reviewing files that changed from the base of the PR and between a793d47 and a8bd8bf.

📒 Files selected for processing (3)
  • apps/website/content/docs/components/(components)/dialog.mdx
  • apps/website/src/components/component-explorer/anatomy-panel.tsx
  • apps/website/src/components/component-explorer/component-explorer.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/website/content/docs/components/(components)/dialog.mdx

Comment thread apps/website/src/components/component-explorer/anatomy-panel.tsx

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

coderabbitai[bot] reacted with eyes emoji

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment on lines +86 to +88
const mutationObserver = new MutationObserver(() => {
scheduleFindAndHighlight();
});

@noahchoii noahchoii Mar 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I noticed that the MutationObserver here calls scheduleFindAndHighlight upon DOM changes. However, scheduleFindAndHighlight returns calculated rects, which are then injected back into the style attributes in highlight-overlay.tsx. This effectively triggers another DOM change.

It seems we've hit an infinite loop: the observer detects a change, modifies the DOM, and then detects its own modification. While it’s limited to the iframe’s document.body, this continuous loop will definitely cause runtime overhead. Also, the infinite logs in React DevTools seem to be a direct symptom of this loop. What are your thoughts on whether we should optimize this?

2026年03月26日.3.59.31.mov

@MaxLee-dev MaxLee-dev Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It seems the error case you reported occurred prior to the documentation site update. This is because the explorer component was not added to the breadcrumb in this branch.

However, I completely agree with your concerns regarding the potential performance issue with MutationObserver. As you mentioned, there is a cycle where findAndHighlight calculates the rects and injects the styles, which triggers a DOM mutation, causing the MutationObserver to detect it again.

Currently, when the highlight part changes, the logic updates according to the flow below:

 findAndHighlight() ──── Calculate rect → setState → Re-render
 │ │
 │ DOM mutation occurs
 │ │
 │ MutationObserver detects
 │ │
 │ scheduleFindAndHighlight()
 │ │
 │ Reserve next frame via rAF
 │ │
 ▼ ▼
 Register ResizeObserver Re-execute findAndHighlight()
 │
 Compare rects ── Identical → Terminate ✅
 │
 Changed → setState → Repeat
 (Terminates after layout stabilizes)

As illustrated above, two defensive mechanisms have been applied to handle this:

1. rAF Debouncing

  • scheduleFindAndHighlight schedules the execution for the next frame using requestAnimationFrame and cancels any previously registered callbacks. Therefore, even if the MutationObserver is triggered multiple times within the same frame, the actual execution is limited to a maximum of once per frame.

2. Cycle Termination via Rect Value Comparison (Newly Added)

  • Inside findAndHighlight, we compare the previous rect values with the new rect values, and if there are no changes, we do an early return. Once the DOM stabilizes, the rect values become identical, preventing setState from being called. This naturally terminates the cycle of re-render → DOM mutation → MutationObserver re-trigger.

Additionally, the reason we set the MutationObserver target to document.body is because portal elements are rendered under the body. To properly detect changes in these portal elements, observation must be done at the body level.

@MaxLee-dev MaxLee-dev marked this pull request as draft April 29, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@coderabbitai coderabbitai[bot] coderabbitai[bot] approved these changes

@noahchoii noahchoii Awaiting requested review from noahchoii noahchoii is a code owner

+1 more reviewer

@gemini-code-assist gemini-code-assist[bot] gemini-code-assist[bot] left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /