From 04fa5ab0543fd513360027349286e47e94b181b8 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Fri, 12 Jun 2026 05:33:21 -0400 Subject: [PATCH] feat(MediaManager): implement MarqueeText component for improved overflow handling in titles --- src/app/components/MediaManager.tsx | 59 +++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/app/components/MediaManager.tsx b/src/app/components/MediaManager.tsx index 7bcd7b7..fd9027a 100644 --- a/src/app/components/MediaManager.tsx +++ b/src/app/components/MediaManager.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react'; +import { lazy, Suspense, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { AlignLeft, ArrowLeft, @@ -126,6 +126,53 @@ function ViewerModeIcon({ mode, className }: { mode: ViewMode; className?: strin // EntryIcon is imported from MediaEntry. +// ─── MarqueeText ────────────────────────────────────────────────────────────── +// Scrolls text left→right when it overflows its container, then back — ping-pong. +// Uses a CSS variable for the per-instance scroll distance so a single keyframe +// declaration covers every instance. + +if (typeof document !== 'undefined' && !document.getElementById('mm-marquee-kf')) { + const s = document.createElement('style'); + s.id = 'mm-marquee-kf'; + s.textContent = `@keyframes mm-scroll { + 0%,12% { transform: translateX(0) } + 88%,100%{ transform: translateX(var(--mm-dx,0px)) } + }`; + document.head.appendChild(s); +} + +function MarqueeText({ children, className }: { children?: string; className?: string }) { + const wrapRef = useRef(null); + const textRef = useRef(null); + const [dx, setDx] = useState(0); + + useLayoutEffect(() => { + const wrap = wrapRef.current; + const text = textRef.current; + if (!wrap || !text) return; + const measure = () => setDx(Math.max(0, text.scrollWidth - wrap.offsetWidth)); + measure(); + const ro = new ResizeObserver(measure); + ro.observe(wrap); + return () => ro.disconnect(); + }, [children]); + + return ( + + 0 ? { + display: 'inline-block', + '--mm-dx': `-${dx}px`, + animation: `mm-scroll ${1.5 + dx / 80}s ease-in-out 1s infinite alternate`, + } as React.CSSProperties : undefined} + > + {children} + + + ); +} + // ─── ActionsModal ───────────────────────────────────────────────────────────── interface FolderManagementActions { @@ -164,7 +211,7 @@ function ActionsModal({ entry, onClose, onOpen, onMount, onDownload, onDuplicate !open && onClose()}> - {entry?.name || '/'} + {entry?.name || '/'} {isFolder ? 'Folder' : humanFileSize(entry?.size ?? 0)} @@ -1269,12 +1316,8 @@ export default function MediaManager({ initialPath, rootPath, title, config, set Mount on Virtual Drive - - {mountEntry?.name} + + {mountEntry?.name}