diff --git a/src/app/features/room/message/Reactions.tsx b/src/app/features/room/message/Reactions.tsx index b03ab2d6d..89f59bc2b 100644 --- a/src/app/features/room/message/Reactions.tsx +++ b/src/app/features/room/message/Reactions.tsx @@ -69,8 +69,15 @@ export const Reactions = as<'div', ReactionsProps>( const measure = useCallback(() => { const container = containerRef.current; if (!container) return; - const rowWidth = container.getBoundingClientRect().width; - const gap = parseFloat(getComputedStyle(container).columnGap) || 8; + // [Gitea #305] The width the chips actually get (content box). The border + // box grows by 4 px while collapsed (margin -2 / padding 2), and measuring + // it made a row within 4 px of fitting flip collapsed↔expanded every frame. + const cs = getComputedStyle(container); + const rowWidth = + container.clientWidth - + (parseFloat(cs.paddingLeft) || 0) - + (parseFloat(cs.paddingRight) || 0); + const gap = parseFloat(cs.columnGap) || 8; const chips = keys ? keys.split('\u0000').map((k) => chipRefs.current.get(k)) : []; const widths = chips.map((el) => el?.getBoundingClientRect().width ?? 0); const moreWidth = moreRef.current?.getBoundingClientRect().width ?? 48; @@ -110,8 +117,21 @@ export const Reactions = as<'div', ReactionsProps>( style={{ ...props.style, ...(collapsed && rowHeight - ? // 2px breathing room so row-1 focus outlines are not clipped. - { maxHeight: rowHeight + 4, overflow: 'hidden', padding: 2, margin: -2 } + ? // 2px breathing room so row-1 focus outlines are not clipped. Longhand + // margins so the timeline's marginTop survives (Gitea #305: the + // `margin` shorthand overrode it and, once removed, cleared it). + { + maxHeight: rowHeight + 4, + overflow: 'hidden', + padding: 2, + marginLeft: -2, + marginRight: -2, + marginBottom: -2, + marginTop: + props.style?.marginTop !== undefined + ? `calc(${typeof props.style.marginTop === 'number' ? `${props.style.marginTop}px` : props.style.marginTop} - 2px)` + : -2, + } : {}), }} ref={(el) => {