-
Notifications
You must be signed in to change notification settings - Fork 11
docs: improve component explorer UI and apply design tokens#471
docs: improve component explorer UI and apply design tokens #471MaxLee-dev wants to merge 39 commits into
Conversation
...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.
...proved typography
- 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>
⚠️ 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
Summary of ChangesHello @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 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
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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 및 성능 개선을 위한 제안 사항을 코드에 남겼습니다. 이 멋진 기능 추가에 감사드립니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scroll과 resize 이벤트에 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]);
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
- 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>
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
panelPropsis wrapped withuseMemo- the handlers are kept stable with
useCallback availablePartsonly 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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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/RadioCardwithdiv[role="option"]inside adiv[role="listbox"]pattern - Removed
aria-pressedentirely — selection state is now expressed viaaria-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.
...s-component-analyzer
...in anatomy panel and component explorer
...component explorer
...for improved accessibility and part selection management
...for improved safety
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
MaxLee-dev
commented
Mar 23, 2026
@coderabbitai review
✅ 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
apps/website/src/app/preview/[component]/preview-wrapper.tsxapps/website/src/components/component-explorer/anatomy-panel.tsxapps/website/src/components/component-explorer/component-explorer.tsxapps/website/src/components/component-explorer/explorer-preview-pane.tsxapps/website/src/components/component-explorer/iframe/highlight-overlay.tsxapps/website/src/components/component-explorer/iframe/use-available-parts-reporter.tsapps/website/src/components/component-explorer/iframe/use-highlight-tracking.tsapps/website/src/components/component-explorer/part-option.tsxapps/website/src/components/component-explorer/use-component-explorer-controller.tsapps/website/src/components/component-explorer/use-part-selection.tsapps/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
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MaxLee-dev
commented
Mar 23, 2026
@coderabbitai review
✅ 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
apps/website/src/app/preview/[component]/preview-wrapper.tsxapps/website/src/components/component-explorer/anatomy-panel.tsxapps/website/src/components/component-explorer/explorer-preview-pane.tsxapps/website/src/components/component-explorer/part-option.tsxapps/website/src/components/component-explorer/types.tsapps/website/src/components/component-explorer/use-component-explorer-controller.tsapps/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
MaxLee-dev
commented
Mar 25, 2026
@coderabbitai review
✅ 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
apps/website/content/docs/components/(components)/dialog.mdxapps/website/src/components/component-explorer/anatomy-panel.tsxapps/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
MaxLee-dev
commented
Mar 25, 2026
@coderabbitai review
✅ 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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
scheduleFindAndHighlightschedules the execution for the next frame usingrequestAnimationFrameand cancels any previously registered callbacks. Therefore, even if theMutationObserveris 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, preventingsetStatefrom being called. This naturally terminates the cycle of re-render → DOM mutation →MutationObserverre-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.
Uh oh!
There was an error while loading. Please reload this page.
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
imageChecklist
Summary by CodeRabbit
New Features
Cross-window Communication
Documentation