Skip to content

Attachment table: keep the search suggestions aligned with the field - #1209

Draft
JeanMarcMilletScality wants to merge 2 commits into
development/1.0from
bugfix/CUI-attachment-search-dropdown-width
Draft

JeanMarcMilletScality wants to merge 2 commits into
development/1.0from
bugfix/CUI-attachment-search-dropdown-width

Conversation

@JeanMarcMilletScality

@JeanMarcMilletScality JeanMarcMilletScality commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

TL;DR — Attachment table: the entity search's suggestion list no longer hangs past the field's edge when the layout narrows, and the field's border and the list now stop being highlighted at the same moment instead of a beat apart.

Context / Why

The suggestion list is anchored to the search box, and until #1201 that box could not change width, so a width measured once was always right. Making the box shrinkable is what exposed the measurement: the field now narrows with its container, and the list did not follow.

🧩 Approach

The width came from a DOM measurement held in React state. It is now two CSS insets on a wrapper that is itself exactly as wide as the field, so there is no width to keep in sync at all.

Before

const [searchWidth, setSearchWidth] = useState('0px');

<SearchBoxContainer
  ref={(element) => {                                                 // ←
    if (element?.firstElementChild) {
      setSearchWidth(
        element.firstElementChild.getBoundingClientRect().width - 2 + 'px',
      );
    }
  }}
>
  <MenuContainer {...getMenuProps()} $width={searchWidth} />          // ←

After

const SearchAnchor = styled.div`
  position: relative;
  width: fit-content;        /* ← the field's width, not the padding box's */
  max-width: 100%;
`;

<SearchBoxContainer>
  <SearchAnchor>
    <StyledSearchInput  />
    <MenuContainer {...getMenuProps()} />   /* left: 0; right: 0 */
  </SearchAnchor>

fit-content is what makes the insets correct: the field hugs its own content, so a wrapper that stretched would put the list on the padding box and leave it wider than the field it belongs to.

Measured — one list held open while the panel is resized underneath it, which is the case the old code could not see, since its measurement only refreshed on render:

Panel Field List before Overhang before List after
600px 287px 287px 0 287px
360px 287px 287px 0 287px
300px 272px 287px +15px 272px
260px 232px 287px +55px 232px
220px 192px 287px +95px 192px

The field only begins to shrink below about 351px, which is why the defect has a floor and stayed unnoticed. Widening back to 600px recovers in both builds, so the stale value was never permanent — just wrong for as long as the panel stayed narrow.

The second change is when the field stops looking active. Its seam with the open list was drawn from :focus-within, and the list from its own open state — two different events:

Clicking outside Before After
mousedown the field drops its seam, because focus has already left nothing — the list is still open
mouseup the list closes, a beat later seam and list go together

Keying the seam on the same open state the list uses collapses that into one transition.

🔍 Review focus

  • 🟡 Moderateorganisms/attachments/AttachmentTable.tsx › StyledSearchInput — the && in the seam rule is load-bearing, not a habit. Input paints its own border from :hover and :focus-within, both of which carry a pseudo-class and outrank a single-class & > div; the old selector won for free because it carried one too. Dropping an & leaves the open state silently unstyled.
  • 🟡 Moderate — same file, the two StyledSearchInput call sites — the removed onBlur was spread after getInputProps(), so it had been overriding downshift's own blur handler. Removing it hands that back: tabbing out of the field now closes the list, which it did not before.
  • Minor — same file › SearchAnchorposition: relative moved off SearchBoxContainer onto the new wrapper. The list is the only absolutely positioned element in the file, so nothing else changes containing block.

🧪 How to test

  1. Open the NarrowPanel attachment story in Storybook.
  2. Click into the entity search so the suggestion list opens.
  3. Keeping it open, narrow the panel without using the frameWidth control — resize the frame element from devtools, so React does not re-render. The list should stay flush with the field; below ~350px the field shrinks and the list should shrink with it.
  4. Click outside. The field's highlight and the list should go at the same moment, not one and then the other.
  5. Focus the field again and press Tab. The list should close.

Follow-up

  • The before/after pair is captured and needs attaching by hand; there is no API for it and no signed-in browser was reachable here. Until it is, the measured table above stands in for it.

🔗 References

What changed

Two comments claimed the search box was a fixed 287px and that this was the table's narrowest point; neither has been true since #1201, and leaving them would send the next reader looking for a floor that no longer exists. One is in this file, the other in the NarrowPanel story.

The searchInputIsFocused state goes with them, along with the list's focused-but-closed bottom border that was its only consumer — with the seam now keyed on the open state, the field keeps its own border in that state and already draws that edge.


🤖 Generated with Claude Code

The suggestion menu took its width from a measurement of the search box,
read in a ref callback and held in state. That refreshed on every render
but never on a resize that causes no render -- a container query, or a
side panel narrowing the content box -- so the menu kept a width the
field no longer had. It could not bite while the box was pinned to one
width; making the box shrinkable is what exposed it.

Anchor the menu to the search container's padding box with left/right
insets instead, so it follows the field with no measurement and no state.
Both insets set means the used width falls out of the over-constrained
equation, which already accounts for the menu's own borders -- so the
`- 2` goes too, and the menu now lines up with the field's outer edge
instead of sitting a pixel short on each side.

Also drops two notes that stopped being true when the box became
shrinkable: it is no longer a fixed 287px, and no longer this table's
narrowest point.
@bert-e

bert-e commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Hello jeanmarcmilletscality,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/bypass_source_branch_lineage Bypass the cross-branch contamination check
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request.
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

…t with the seam

The previous commit anchored the menu to `SearchBoxContainer`'s padding box,
which is wider than the field, since the field hugs its own content. A
`SearchAnchor` wrapper with `width: fit-content` becomes the menu's containing
block instead, so `left: 0; right: 0` resolves to the field's width. Measured
over one menu held open while the panel resized underneath it with no re-render,
600px down to 220px and back: the menu tracked the field to 0px at every step,
against an overhang reaching 95px before.

The field's seam with the open menu moves off `:focus-within` onto the menu's
own open state. An outside click blurs the input on `mousedown` while the menu
closes on the `mouseup` that follows, so a focus-driven seam snapped back under
a menu that was still open. `&&` is required on the new rule: `Input` paints its
border from `:hover` and `:focus-within`, both of which outrank a single-class
`& > div`.

Dropping the consumer's `onBlur` also restores downshift's own, which it had
been overriding by being spread after `getInputProps()`, so tabbing out of the
field now closes the menu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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