fix(channels): stable tag column width, no popover flash, no sync wrap (R8 S1 UAT)

Three UAT refinements on the tag cell + popover platform:

- Fixed tag-column width (w-[15rem], was max-w): under table-layout:auto a content-
  driven cell re-solved every column's width on each chip added, so the table visibly
  shifted (and the "+N" count flickered) until the cap was hit. A fixed footprint makes
  adding/removing tags move nothing (verified: column x unchanged, "+N" count stable).
- No popover flash: gate the floating surface's visibility on Floating UI's isPositioned
  so it never paints at (0,0) top-left for a frame before the transform lands.
- Sync cell no longer wraps: flex-nowrap so the recent/full badges stay on one line
  (the column grows to fit them instead of breaking to two rows).
This commit is contained in:
2026-07-30 23:33:50 +02:00
parent cfa8be950d
commit 0fd95c15a1
3 changed files with 25 additions and 10 deletions
+18 -5
View File
@@ -64,7 +64,7 @@ export function useAnchoredPopover(opts: AnchoredPopoverOptions) {
// null when not inside a <FloatingTree> — the hook still works, just without nested-layer awareness.
const nodeId = useFloatingNodeId();
const { refs, floatingStyles, context, middlewareData } = useFloating({
const { refs, floatingStyles, context, middlewareData, isPositioned } = useFloating({
nodeId,
open: opts.open,
onOpenChange: opts.onOpenChange,
@@ -108,6 +108,7 @@ export function useAnchoredPopover(opts: AnchoredPopoverOptions) {
refs,
floatingStyles,
context,
isPositioned,
referenceHidden: middlewareData.hide?.referenceHidden ?? false,
...interactions,
};
@@ -134,8 +135,16 @@ export function PopoverSurface({
focusManaged?: boolean;
initialFocus?: number | React.MutableRefObject<HTMLElement | null>;
}) {
const { context, refs, floatingStyles, getFloatingProps, nodeId, referenceHidden, open } =
popover;
const {
context,
refs,
floatingStyles,
getFloatingProps,
nodeId,
referenceHidden,
isPositioned,
open,
} = popover;
const mergedRef = useMergeRefs([refs.setFloating, floatingRef ?? null]);
const floatingProps = getFloatingProps();
@@ -149,8 +158,12 @@ export function PopoverSurface({
style={{
...(floatingProps.style as CSSProperties | undefined),
...floatingStyles,
// Trigger scrolled out of its clipping scroller → hide-and-follow rather than float over chrome.
...(referenceHidden ? { visibility: "hidden", pointerEvents: "none" } : null),
// Hide until (a) the first position is measured — else it flashes at top-left (0,0) before
// the transform lands — and (b) while the trigger is scrolled out of its clipping scroller
// (hide-and-follow rather than float over the chrome).
...(!isPositioned || referenceHidden
? { visibility: "hidden", pointerEvents: "none" }
: null),
...style,
}}
>
+6 -4
View File
@@ -190,10 +190,12 @@ export function ChannelTagsControl({
const fade = useScrollFade();
return (
// max-w caps the cell's MAX-CONTENT so `table-layout:auto` can't ignore the column width and let
// an over-tagged row stretch the whole table (the documented channel/About-column trap). The chip
// row then greedily fits within it and collapses the rest into "+N".
<div className="flex items-center gap-1 w-full max-w-[15rem] min-w-0">
// FIXED width (not just max-w): under `table-layout:auto` a content-driven cell re-solves the
// whole table's column widths every time a chip is added — the table visibly shifts until the cap
// is hit. A fixed width makes the cell's footprint constant, so adding/removing tags never moves
// any column; the chip row greedily fits within it and collapses the rest into "+N". (max-w-full
// lets a narrower card shrink it.)
<div className="flex items-center gap-1 w-[15rem] max-w-full min-w-0">
<div ref={rowRef} className="flex items-center gap-1 min-w-0 flex-1 overflow-hidden">
{attached.slice(0, visible).map((tg) => (
<TagChip
+1 -1
View File
@@ -1003,7 +1003,7 @@ function SyncCell({ c }: { c: ManagedChannel }) {
);
}
return (
<div className="flex flex-wrap items-center gap-1">
<div className="flex flex-nowrap items-center gap-1 whitespace-nowrap">
<SyncBadge
ok={c.recent_synced}
label={t("channels.row.recent")}