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:
@@ -64,7 +64,7 @@ export function useAnchoredPopover(opts: AnchoredPopoverOptions) {
|
|||||||
// null when not inside a <FloatingTree> — the hook still works, just without nested-layer awareness.
|
// null when not inside a <FloatingTree> — the hook still works, just without nested-layer awareness.
|
||||||
const nodeId = useFloatingNodeId();
|
const nodeId = useFloatingNodeId();
|
||||||
|
|
||||||
const { refs, floatingStyles, context, middlewareData } = useFloating({
|
const { refs, floatingStyles, context, middlewareData, isPositioned } = useFloating({
|
||||||
nodeId,
|
nodeId,
|
||||||
open: opts.open,
|
open: opts.open,
|
||||||
onOpenChange: opts.onOpenChange,
|
onOpenChange: opts.onOpenChange,
|
||||||
@@ -108,6 +108,7 @@ export function useAnchoredPopover(opts: AnchoredPopoverOptions) {
|
|||||||
refs,
|
refs,
|
||||||
floatingStyles,
|
floatingStyles,
|
||||||
context,
|
context,
|
||||||
|
isPositioned,
|
||||||
referenceHidden: middlewareData.hide?.referenceHidden ?? false,
|
referenceHidden: middlewareData.hide?.referenceHidden ?? false,
|
||||||
...interactions,
|
...interactions,
|
||||||
};
|
};
|
||||||
@@ -134,8 +135,16 @@ export function PopoverSurface({
|
|||||||
focusManaged?: boolean;
|
focusManaged?: boolean;
|
||||||
initialFocus?: number | React.MutableRefObject<HTMLElement | null>;
|
initialFocus?: number | React.MutableRefObject<HTMLElement | null>;
|
||||||
}) {
|
}) {
|
||||||
const { context, refs, floatingStyles, getFloatingProps, nodeId, referenceHidden, open } =
|
const {
|
||||||
popover;
|
context,
|
||||||
|
refs,
|
||||||
|
floatingStyles,
|
||||||
|
getFloatingProps,
|
||||||
|
nodeId,
|
||||||
|
referenceHidden,
|
||||||
|
isPositioned,
|
||||||
|
open,
|
||||||
|
} = popover;
|
||||||
const mergedRef = useMergeRefs([refs.setFloating, floatingRef ?? null]);
|
const mergedRef = useMergeRefs([refs.setFloating, floatingRef ?? null]);
|
||||||
const floatingProps = getFloatingProps();
|
const floatingProps = getFloatingProps();
|
||||||
|
|
||||||
@@ -149,8 +158,12 @@ export function PopoverSurface({
|
|||||||
style={{
|
style={{
|
||||||
...(floatingProps.style as CSSProperties | undefined),
|
...(floatingProps.style as CSSProperties | undefined),
|
||||||
...floatingStyles,
|
...floatingStyles,
|
||||||
// Trigger scrolled out of its clipping scroller → hide-and-follow rather than float over chrome.
|
// Hide until (a) the first position is measured — else it flashes at top-left (0,0) before
|
||||||
...(referenceHidden ? { visibility: "hidden", pointerEvents: "none" } : null),
|
// 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,
|
...style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -190,10 +190,12 @@ export function ChannelTagsControl({
|
|||||||
const fade = useScrollFade();
|
const fade = useScrollFade();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// max-w caps the cell's MAX-CONTENT so `table-layout:auto` can't ignore the column width and let
|
// FIXED width (not just max-w): under `table-layout:auto` a content-driven cell re-solves the
|
||||||
// an over-tagged row stretch the whole table (the documented channel/About-column trap). The chip
|
// whole table's column widths every time a chip is added — the table visibly shifts until the cap
|
||||||
// row then greedily fits within it and collapses the rest into "+N".
|
// is hit. A fixed width makes the cell's footprint constant, so adding/removing tags never moves
|
||||||
<div className="flex items-center gap-1 w-full max-w-[15rem] min-w-0">
|
// 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">
|
<div ref={rowRef} className="flex items-center gap-1 min-w-0 flex-1 overflow-hidden">
|
||||||
{attached.slice(0, visible).map((tg) => (
|
{attached.slice(0, visible).map((tg) => (
|
||||||
<TagChip
|
<TagChip
|
||||||
|
|||||||
@@ -1003,7 +1003,7 @@ function SyncCell({ c }: { c: ManagedChannel }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-1">
|
<div className="flex flex-nowrap items-center gap-1 whitespace-nowrap">
|
||||||
<SyncBadge
|
<SyncBadge
|
||||||
ok={c.recent_synced}
|
ok={c.recent_synced}
|
||||||
label={t("channels.row.recent")}
|
label={t("channels.row.recent")}
|
||||||
|
|||||||
Reference in New Issue
Block a user