Skip to content

refactor: header improvements#1001

Merged
benlife5 merged 17 commits intomainfrom
wip-header-improvements
Jan 28, 2026
Merged

refactor: header improvements#1001
benlife5 merged 17 commits intomainfrom
wip-header-improvements

Conversation

@benlife5
Copy link
Copy Markdown
Contributor

@benlife5 benlife5 commented Jan 26, 2026

@benlife5 benlife5 added the create-dev-release Triggers dev release workflow label Jan 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Warning: Component files have been updated but no migrations have been added. See https://github.com/yext/visual-editor/blob/main/packages/visual-editor/src/components/migrations/README.md for more information.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jan 26, 2026

commit: c1bb9cc

@benlife5 benlife5 marked this pull request as ready for review January 27, 2026 21:51
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 27, 2026

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • packages/visual-editor/src/vite-plugin/defaultLayoutData.ts

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

  • 🔍 Trigger a full review

Walkthrough

Adds responsive header link overflow and collapse behavior with editor preview measurement. HeaderLinks gains a new collapsedLinks field, measurement scaffolding, overflow calculation, and an overflow dropdown; it exports resolveFields. useOverflow signature now accepts containerAdjustment. PrimaryHeaderSlot introduces a CTAContainer and editor hamburger overlay wiring via registerOverlayPortal. ExpandedHeader and default layout data are updated to include parentData, collapsedLinks, openInNewTab, and eventName fields. A migration for header link updates and a rename of removeDuplicateImageActionBarsremoveDuplicateActionBars were added. Various locale files receive new keys for collapsed links and tooltip text.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Editor/Client
    participant HeaderLinks as HeaderLinks Component
    participant Measure as Measurement Scaffolding
    participant Overflow as useOverflow Hook
    participant Renderer as Link Renderer

    Editor->>HeaderLinks: Mount with data (links, collapsedLinks, parentData)
    activate HeaderLinks

    HeaderLinks->>Measure: create hidden measurement divs (containerRef, contentRef)
    Note right of Measure: hidden/absolute divs provide widths

    Measure->>Overflow: computeOverflow(containerRef, contentRef, containerAdjustment)
    activate Overflow
    Overflow->>Overflow: adjustedWidth = container.width - containerAdjustment
    Overflow->>Overflow: overflow = content.scrollWidth > adjustedWidth
    Overflow-->>Measure: return visibleItems and overflowItems
    deactivate Overflow

    alt Overflow detected
        HeaderLinks->>Renderer: render visibleItems + overflow dropdown (populate with overflowItems)
    else No overflow
        HeaderLinks->>Renderer: render all links inline
    end

    Renderer->>Editor: display links (apply collapsedLinks rules based on parentData.type)
    deactivate HeaderLinks
Loading

Possibly related PRs

Suggested reviewers

  • briantstephan
  • mkilpatrick
  • asanehisa
🚥 Pre-merge checks | ❌ 2
❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'refactor: header improvements' is vague and overly broad, using generic terms that don't convey specific meaningful information about the substantive changes in the changeset. Consider a more specific title that captures the primary feature, such as 'feat: implement responsive header link overflow handling with collapsed links support' or similar.
Description check ❓ Inconclusive The PR description contains only a URL link with no textual explanation of the changes, objectives, or rationale, making it impossible to assess the changeset from the description alone. Add a detailed description explaining the changes, motivation, and impact rather than relying solely on an external link.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/visual-editor/src/internal/utils/removeDuplicateImageActionBars.ts (1)

3-38: Hide all duplicates, not just the first match.

findTargetDiv returns a single node, so only one duplicate per target gets hidden. If multiple duplicates exist for the same translatedTarget, the extras remain visible. Consider returning all matches and hiding them all.

✅ Suggested fix
-const findTargetDiv = (
-  iframe: HTMLIFrameElement,
-  translatedTarget: string
-): HTMLDivElement | null => {
+const findTargetDivs = (
+  iframe: HTMLIFrameElement,
+  translatedTarget: string
+): HTMLDivElement[] => {
   // Get all action bar divs
   const targets = iframe?.contentDocument?.querySelectorAll(
     'div[class^="_ActionBar"]'
   );

   if (!targets) {
-    return null;
+    return [];
   }

   // For each div, check the great-grandparent and grandchild for specific criteria
+  const matches: HTMLDivElement[] = [];
   for (const targetDiv of targets) {
     const greatGrandparent =
       targetDiv.parentElement?.parentElement?.parentElement;

     // The great-grandparent of the duplicate bar will have a height of 0px
     if (
       greatGrandparent &&
       greatGrandparent.tagName === "DIV" &&
       greatGrandparent.style.height === "0px"
     ) {
       const grandchild = targetDiv.querySelector(
         "div[class^='_ActionBar-label']"
       );

       // The grandchild of the duplicate bar will have the translation of "Image"
       if (grandchild && grandchild.textContent.includes(translatedTarget)) {
-        return targetDiv as HTMLDivElement;
+        matches.push(targetDiv as HTMLDivElement);
       }
     }
   }

-  return null;
+  return matches;
 };
@@
-  targets.forEach((target) => {
-    const targetDiv = findTargetDiv(iframe, target);
-
-    // Hide the duplicate action bar if found
-    if (targetDiv) {
-      targetDiv.style.display = "none";
-    }
-  });
+  targets.forEach((target) => {
+    const targetDivs = findTargetDivs(iframe, target);
+    targetDivs.forEach((div) => {
+      div.style.display = "none";
+    });
+  });

Also applies to: 63-69

🤖 Fix all issues with AI agents
In `@packages/visual-editor/src/components/header/HeaderLinks.tsx`:
- Around line 151-154: The effect uses hamburgerButtonRef.current in its
dependency array which won't trigger re-runs because React doesn't track
ref.current; replace this with a callback/state ref: add state like
[hamburgerButton, setHamburgerButton], change the element's ref to
ref={setHamburgerButton} (or implement a callback ref) and update the useEffect
to call registerOverlayPortal(hamburgerButton) with hamburgerButton in its
dependency array; ensure you also handle any necessary cleanup (e.g.,
unregisterOverlayPortal) inside the effect if applicable.

In `@packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx`:
- Around line 130-134: The effect currently calls
registerOverlayPortal(hamburgerButtonRef.current) but does not return its
cleanup and incorrectly uses hamburgerButtonRef.current in the dependency array;
update the React.useEffect that references registerOverlayPortal and
hamburgerButtonRef so it returns the cleanup function from registerOverlayPortal
(e.g., const cleanup = registerOverlayPortal(hamburgerButtonRef.current); return
cleanup;) and use hamburgerButtonRef (the ref object) in the dependency array
instead of hamburgerButtonRef.current to avoid unnecessary re-runs—locate the
effect in PrimaryHeaderSlot.tsx where React.useEffect, registerOverlayPortal,
and hamburgerButtonRef are used and apply these changes.

In `@packages/visual-editor/src/hooks/useOverflow.ts`:
- Around line 25-28: The ResizeObserver callback in useOverflow computes
hasOverflow using container.clientWidth - containerAdjustment which can go
negative; clamp the adjusted width to a minimum of 0 before comparing to
content.scrollWidth. Inside the ResizeObserver callback (in useOverflow),
compute const adjustedWidth = Math.max(0, container.clientWidth -
containerAdjustment) and then set hasOverflow = content.scrollWidth >
adjustedWidth before calling setIsOverflowing; update any related variable names
accordingly to ensure no negative values are used in the comparison.

In `@packages/visual-editor/src/internal/utils/removeDuplicateImageActionBars.ts`:
- Around line 56-60: The targets array in removeDuplicateImageActionBars.ts uses
pt("callToAction", "callToAction") which provides a non-user-facing fallback
label; change the fallback to a human-readable string (e.g., "Call to Action" or
"Call to action") so the pt helper and the duplicate-removal logic in
removeDuplicateImageActionBars (targets array) will match rendered labels when
translations are missing; update the second argument of pt for the
"callToAction" key accordingly.

Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx
Comment thread packages/visual-editor/src/components/header/PrimaryHeaderSlot.tsx
Comment thread packages/visual-editor/src/hooks/useOverflow.ts
Copy link
Copy Markdown
Contributor

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

🤖 Fix all issues with AI agents
In `@packages/visual-editor/locales/platform/cs/visual-editor.json`:
- Line 193: Update the Czech translation value for the key
"alwaysCollapsedLinks" to use an imperative phrasing for consistency (e.g.,
change "Vždy sbalené odkazy" to an imperative like "Vždy sbalit odkazy" or "Vždy
sbalovat odkazy"); locate the "alwaysCollapsedLinks" entry in
packages/visual-editor/locales/platform/cs/visual-editor.json and replace the
string value with the chosen imperative form.
🧹 Nitpick comments (2)
packages/visual-editor/locales/platform/nb/visual-editor.json (1)

188-189: Consider aligning “koblinger” with the existing “lenker” terminology.

Most “links” strings in this locale file use “lenker”. For UI consistency, you may want to align these two new labels with the prevailing term.

♻️ Suggested copy alignment
-    "alwaysCollapsedLinks": "Alltid skjulte koblinger",
+    "alwaysCollapsedLinks": "Alltid skjulte lenker",
...
-    "linksTooltip": "Koblinger vil automatisk kollapse hvis visningsporten er for smal",
+    "linksTooltip": "Lenker vil automatisk kollapse hvis visningsporten er for smal",

Also applies to: 296-296

packages/visual-editor/locales/platform/ro/visual-editor.json (1)

296-296: Minor grammatical polish suggestion.

The translation uses "Linkuri" (indefinite), but "Linkurile" (with the definite article) would be more natural in Romanian when referring to specific links in context.

✏️ Suggested improvement
-    "linksTooltip": "Linkuri se vor restrânge automat dacă fereastra de vizualizare este prea îngustă",
+    "linksTooltip": "Linkurile se vor restrânge automat dacă fereastra de vizualizare este prea îngustă",

Comment thread packages/visual-editor/locales/platform/cs/visual-editor.json Outdated
@benlife5 benlife5 changed the title refactor: header improvements [wip] refactor: header improvements Jan 28, 2026
Copy link
Copy Markdown
Contributor

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

🤖 Fix all issues with AI agents
In `@packages/visual-editor/src/components/header/HeaderLinks.tsx`:
- Around line 161-168: windowWidth is computed once per render via
getWindow()?.innerWidth which doesn't update on window resize causing stale
collapsed-links visibility; change it to a stateful, reactive value: create a
state like const [windowWidth, setWindowWidth] = useState(() =>
getWindow()?.innerWidth || 1024) and add a useEffect that listens to window
'resize' events and calls setWindowWidth(getWindow()?.innerWidth || 1024) (with
optional debounce) and cleans up the listener; update every place that currently
computes getWindow()?.innerWidth (including other occurrences around the header
logic that affect collapsed links and breakpoints) to use this stateful
windowWidth so isOverflow and conditional UI react to resizes (references:
windowWidth, getWindow, useOverflow, navRef, measureContainerRef, isSecondary,
validAlwaysCollapsedLinks).
- Around line 158-160: The code currently always computes
validAlwaysCollapsedLinks from data.collapsedLinks which leaves collapsed links
present when the header switches to Primary; update the logic so
validAlwaysCollapsedLinks is only derived when the header is in Secondary mode
(e.g., check the component’s isSecondary/headerType prop or data.headerType) and
otherwise set it to an empty array; similarly apply this guard wherever
collapsedLinks are used later (the block that handles links at lines ~190-209)
so empty-state early returns for Primary work correctly.

In `@packages/visual-editor/src/internal/utils/removeDuplicateActionBars.ts`:
- Around line 31-34: In removeDuplicateActionBars, guard against
grandchild.textContent being null before calling .includes: change the condition
that currently reads "if (grandchild &&
grandchild.textContent.includes(translatedTarget))" to first ensure
grandchild.textContent is non-null (e.g., grandchild && grandchild.textContent
!= null && grandchild.textContent.includes(translatedTarget) or use optional
chaining like grandchild?.textContent?.includes(translatedTarget)); keep
returning targetDiv as HTMLDivElement when the guard passes.
🧹 Nitpick comments (1)
packages/visual-editor/src/internal/components/InternalLayoutEditor.tsx (1)

420-427: Pre-existing: Hooks called inside nested actionBar function.

Static analysis correctly flags that hooks (useGetPuck, usePuck, React.useEffect) are called inside a nested function. While Puck likely treats actionBar as a component internally, this pattern can cause issues if React doesn't recognize it as a stable component identity.

The function rename on line 427 is correct. However, consider extracting actionBar to a standalone component defined at module level to satisfy the Rules of Hooks and improve maintainability:

const ActionBarOverride = ({ children, label }: { children: React.ReactNode; label: string }) => {
  // hooks called at top level of component
  ...
};

// Then in overrides:
actionBar: ActionBarOverride,

This is a pre-existing pattern, so addressing it can be deferred.

Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx Outdated
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx Outdated
Comment thread packages/visual-editor/locales/platform/en/visual-editor.json Outdated
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx
Comment thread packages/visual-editor/src/components/header/ExpandedHeader.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/visual-editor/src/components/header/ExpandedHeader.tsx`:
- Around line 213-219: Remove the debug console.log call in ExpandedHeader.tsx:
delete the console.log("EH data", data) statement after the setDeep(...) call
(the debug line that references variable data), ensuring no stray
console.debug/console.log remains in the ExpandedHeader component; if structured
logging is required instead, replace it with the app's logger utility rather
than leaving a console call.

In `@packages/visual-editor/src/components/header/HeaderLinks.tsx`:
- Line 141: Remove the debug console.log call in the HeaderLinks component:
delete the line console.log("render", data, parentData) found in HeaderLinks.tsx
(inside the HeaderLinks render/function body) before merging; if you need
telemetry keep a proper logger or use conditional debug logging instead and scan
the component for any other stray console.* calls to remove or replace.

Comment thread packages/visual-editor/src/components/header/ExpandedHeader.tsx Outdated
Comment thread packages/visual-editor/src/components/header/HeaderLinks.tsx Outdated
mkilpatrick
mkilpatrick previously approved these changes Jan 28, 2026
asanehisa
asanehisa previously approved these changes Jan 28, 2026
Copy link
Copy Markdown
Contributor

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

🤖 Fix all issues with AI agents
In `@packages/visual-editor/src/components/header/ExpandedHeader.tsx`:
- Around line 213-217: The setDeep call on the data variable is using an
incorrect path that includes a non-existent nested SecondaryHeaderSlot[0];
update the path passed to setDeep from
"props.slots.SecondaryHeaderSlot[0].props.slots.SecondaryHeaderSlot[0].props.slots.LinksSlot[0].props.parentData.type"
to
"props.slots.SecondaryHeaderSlot[0].props.slots.LinksSlot[0].props.parentData.type"
(referencing the setDeep call itself), and if the default layout already seeds
parentData.type as "Secondary" remove the entire setDeep invocation to avoid a
redundant mutation.
🧹 Nitpick comments (1)
packages/visual-editor/src/components/header/HeaderLinks.tsx (1)

219-235: Measurement div approach is correct, but linkRefs appear unused.

The hidden measurement div pattern is appropriate for calculating overflow. However, linkRefs is populated at line 228 but doesn't appear to be read anywhere in the component. If this is for future use, consider adding a comment; otherwise, it can be removed to reduce unnecessary ref tracking.

♻️ If unused, remove the ref assignment
-            <li
-              key={`${type.toLowerCase()}.${index}`}
-              ref={(el) => (linkRefs.current[index] = el)}
-              className={themeManagerCn("py-4 md:py-0")}
-            >
+            <li
+              key={`${type.toLowerCase()}.${index}`}
+              className={themeManagerCn("py-4 md:py-0")}
+            >

And remove line 146:

-  const linkRefs = React.useRef<Array<HTMLLIElement | null>>([]);

Comment thread packages/visual-editor/src/components/header/ExpandedHeader.tsx Outdated
@benlife5 benlife5 dismissed stale reviews from asanehisa and mkilpatrick via 8df870f January 28, 2026 18:34
@benlife5
Copy link
Copy Markdown
Contributor Author

Was hoping to avoid migration but the resolveData for HeaderLinks was being weird and so it was just easier to set the primary/secondary value in a migration

mkilpatrick
mkilpatrick previously approved these changes Jan 28, 2026
asanehisa
asanehisa previously approved these changes Jan 28, 2026
Copy link
Copy Markdown
Contributor

@asanehisa asanehisa left a comment

Choose a reason for hiding this comment

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

did u wanna update the version in defaultLayoutData?

@benlife5 benlife5 dismissed stale reviews from asanehisa and mkilpatrick via c1bb9cc January 28, 2026 18:59
@benlife5
Copy link
Copy Markdown
Contributor Author

did u wanna update the version in defaultLayoutData?

Good call, updated with the new defaults for photo gallery/product section too

Copy link
Copy Markdown
Contributor

@asanehisa asanehisa left a comment

Choose a reason for hiding this comment

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

thanks!!!

@benlife5 benlife5 merged commit 10c2e4e into main Jan 28, 2026
18 checks passed
@benlife5 benlife5 deleted the wip-header-improvements branch January 28, 2026 19:40
@coderabbitai coderabbitai Bot mentioned this pull request Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

create-dev-release Triggers dev release workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants