diff --git a/src/components/friends/views/friends-bar/FriendsBarView.tsx b/src/components/friends/views/friends-bar/FriendsBarView.tsx index de51519..9234a07 100644 --- a/src/components/friends/views/friends-bar/FriendsBarView.tsx +++ b/src/components/friends/views/friends-bar/FriendsBarView.tsx @@ -1,11 +1,23 @@ -import { FC, useRef, useState } from 'react'; +import { FC, useLayoutEffect, useRef, useState } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { LocalizeText, MessengerFriend } from '../../../../api'; import { FriendBarItemView } from './FriendBarItemView'; import { motion, AnimatePresence, Variants } from 'framer-motion'; +// Hard cap on simultaneously-shown friend chips. The effective count is +// reduced below this when the bar would otherwise overflow its (clipped) +// slot in the toolbar — see the width measurement below. const MAX_DISPLAY_COUNT = 3; +// Layout constants mirrored from FriendBarItemView / the flex gaps here, used +// to compute how many friend chips fit in the available width. A "slot" is one +// w-[132px] button plus the gap-[6px] that precedes it. +const ITEM_SLOT = 138; // 132px chip + 6px gap (friend chip and search chip) +const ARROWS_WIDTH = 52; // two w-[20px] arrows, each + 6px gap +const REQUEST_SLOT = 120; // requests chip (only present when requestsCount > 0) +const BASE_PAD = 8; // container px-[2px] + a little slack +const RIGHT_SAFE = 24; // right inset (right-0/right-3) + pr-3 safety margin + // Mirrored from Toolbar to keep physics identical const containerVariants: Variants = { hidden: {}, @@ -23,9 +35,55 @@ export const FriendBarView: FC<{ onlineFriends: MessengerFriend[]; requestsCount { const { onlineFriends = [], requestsCount = 0 } = props; const [ indexOffset, setIndexOffset ] = useState(0); + const [ maxVisible, setMaxVisible ] = useState(MAX_DISPLAY_COUNT); const elementRef = useRef(null); - const hasScrollableFriends = (onlineFriends.length > MAX_DISPLAY_COUNT); - const visibleFriends = onlineFriends.slice(indexOffset, (indexOffset + MAX_DISPLAY_COUNT)); + + // Auto-fit the visible friend count to the room actually available between + // the bar's left edge and the right side of the viewport. The bar lives in + // a `overflow-x: clip` toolbar slot, so anything that doesn't fit would be + // silently cut off (the scroll arrow / search button disappear). The bar's + // left edge is stable (it sits after fixed-width toolbar icons), so growing + // or shrinking the chip count never moves it — no measurement feedback loop. + useLayoutEffect(() => + { + const element = elementRef.current; + + if(!element) return; + + const measure = () => + { + const left = element.getBoundingClientRect().left; + const available = window.innerWidth - left - RIGHT_SAFE; + const fixed = ARROWS_WIDTH + ITEM_SLOT /* search chip */ + BASE_PAD + ((requestsCount > 0) ? REQUEST_SLOT : 0); + const fit = Math.floor((available - fixed) / ITEM_SLOT); + const next = Math.max(1, Math.min(MAX_DISPLAY_COUNT, fit)); + + setMaxVisible(prev => ((prev === next) ? prev : next)); + }; + + measure(); + + const observer = new ResizeObserver(measure); + + observer.observe(document.documentElement); + window.addEventListener('resize', measure); + + return () => + { + observer.disconnect(); + window.removeEventListener('resize', measure); + }; + }, [ requestsCount, onlineFriends.length ]); + + // `safeOffset` is the offset clamped to the current list/fit. Every read + // below uses it, so a stale `indexOffset` (after the list shrinks or the fit + // grows) renders correctly and self-corrects on the next arrow click — no + // write-back effect needed. + const maxOffset = Math.max(0, (onlineFriends.length - maxVisible)); + const safeOffset = Math.min(indexOffset, maxOffset); + const canScrollLeft = (safeOffset > 0); + const canScrollRight = (safeOffset < maxOffset); + const visibleFriends = onlineFriends.slice(safeOffset, (safeOffset + maxVisible)); return ( }
{ - if(indexOffset > 0) setIndexOffset(indexOffset - 1); + if(canScrollLeft) setIndexOffset(safeOffset - 1); } } > @@ -94,10 +152,10 @@ export const FriendBarView: FC<{ onlineFriends: MessengerFriend[]; requestsCount
MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1)))) ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer hover:text-white active:scale-95' }` } + className={ `flex h-[34px] w-[20px] items-center justify-center text-white/80 transition-all ${ !canScrollRight ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer hover:text-white active:scale-95' }` } onClick={ () => { - if((onlineFriends.length > MAX_DISPLAY_COUNT) && ((indexOffset + MAX_DISPLAY_COUNT) <= (onlineFriends.length - 1))) setIndexOffset(indexOffset + 1); + if(canScrollRight) setIndexOffset(safeOffset + 1); } } >