Browse Source

fixed tab selection

imwald
Silberengel 4 months ago
parent
commit
6371b9253b
  1. 27
      src/components/NoteInteractions/Tabs.tsx
  2. 39
      src/components/Tabs/index.tsx

27
src/components/NoteInteractions/Tabs.tsx

@ -22,7 +22,8 @@ export function Tabs({
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const tabRefs = useRef<(HTMLDivElement | null)[]>([]) const tabRefs = useRef<(HTMLDivElement | null)[]>([])
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0 }) const containerRef = useRef<HTMLDivElement | null>(null)
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0, top: 0 })
// Filter tabs based on hideRepostsAndQuotes // Filter tabs based on hideRepostsAndQuotes
const visibleTabs = hideRepostsAndQuotes const visibleTabs = hideRepostsAndQuotes
@ -32,13 +33,24 @@ export function Tabs({
useEffect(() => { useEffect(() => {
setTimeout(() => { setTimeout(() => {
const activeIndex = visibleTabs.findIndex((tab) => tab.value === selectedTab) const activeIndex = visibleTabs.findIndex((tab) => tab.value === selectedTab)
if (activeIndex >= 0 && tabRefs.current[activeIndex]) { if (activeIndex >= 0 && tabRefs.current[activeIndex] && containerRef.current) {
const activeTab = tabRefs.current[activeIndex] const activeTab = tabRefs.current[activeIndex]
const { offsetWidth, offsetLeft } = activeTab const container = containerRef.current
const { offsetWidth, offsetLeft, offsetHeight } = activeTab
// Get the container's top position relative to the viewport
const containerTop = container.getBoundingClientRect().top
const tabTop = activeTab.getBoundingClientRect().top
// Calculate the indicator's top position relative to the container
// Position it at the bottom of the active tab's row
const relativeTop = tabTop - containerTop + offsetHeight
const padding = 32 // 16px padding on each side const padding = 32 // 16px padding on each side
setIndicatorStyle({ setIndicatorStyle({
width: offsetWidth - padding, width: offsetWidth - padding,
left: offsetLeft + padding / 2 left: offsetLeft + padding / 2,
top: relativeTop - 4 // 4px for the indicator height (1px) + spacing
}) })
} }
}, 20) // ensure tabs are rendered before calculating }, 20) // ensure tabs are rendered before calculating
@ -46,7 +58,7 @@ export function Tabs({
return ( return (
<div className="w-full"> <div className="w-full">
<div className="flex flex-wrap relative gap-1"> <div ref={containerRef} className="flex flex-wrap relative gap-1">
{visibleTabs.map((tab, index) => ( {visibleTabs.map((tab, index) => (
<div <div
key={tab.value} key={tab.value}
@ -61,10 +73,11 @@ export function Tabs({
</div> </div>
))} ))}
<div <div
className="absolute bottom-0 h-1 bg-primary rounded-full transition-all duration-500" className="absolute h-1 bg-primary rounded-full transition-all duration-500"
style={{ style={{
width: `${indicatorStyle.width}px`, width: `${indicatorStyle.width}px`,
left: `${indicatorStyle.left}px` left: `${indicatorStyle.left}px`,
top: `${indicatorStyle.top}px`
}} }}
/> />
</div> </div>

39
src/components/Tabs/index.tsx

@ -25,31 +25,43 @@ export default function Tabs({
const { deepBrowsing, lastScrollTop } = useDeepBrowsing() const { deepBrowsing, lastScrollTop } = useDeepBrowsing()
const tabRefs = useRef<(HTMLDivElement | null)[]>([]) const tabRefs = useRef<(HTMLDivElement | null)[]>([])
const containerRef = useRef<HTMLDivElement | null>(null) const containerRef = useRef<HTMLDivElement | null>(null)
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0 }) const tabsContainerRef = useRef<HTMLDivElement | null>(null)
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0, top: 0 })
const isUpdatingRef = useRef(false) const isUpdatingRef = useRef(false)
const lastStyleRef = useRef({ width: 0, left: 0 }) const lastStyleRef = useRef({ width: 0, left: 0, top: 0 })
const updateIndicatorPosition = useCallback(() => { const updateIndicatorPosition = useCallback(() => {
// Prevent multiple simultaneous updates // Prevent multiple simultaneous updates
if (isUpdatingRef.current) return if (isUpdatingRef.current) return
const activeIndex = tabs.findIndex((tab) => tab.value === value) const activeIndex = tabs.findIndex((tab) => tab.value === value)
if (activeIndex >= 0 && tabRefs.current[activeIndex]) { if (activeIndex >= 0 && tabRefs.current[activeIndex] && tabsContainerRef.current) {
const activeTab = tabRefs.current[activeIndex] const activeTab = tabRefs.current[activeIndex]
const { offsetWidth, offsetLeft } = activeTab const tabsContainer = tabsContainerRef.current
const { offsetWidth, offsetLeft, offsetHeight } = activeTab
const padding = 24 // 12px padding on each side const padding = 24 // 12px padding on each side
// Get the container's top position relative to the viewport
const containerTop = tabsContainer.getBoundingClientRect().top
const tabTop = activeTab.getBoundingClientRect().top
// Calculate the indicator's top position relative to the container
// Position it at the bottom of the active tab's row
const relativeTop = tabTop - containerTop + offsetHeight
const newWidth = offsetWidth - padding const newWidth = offsetWidth - padding
const newLeft = offsetLeft + padding / 2 const newLeft = offsetLeft + padding / 2
const newTop = relativeTop - 4 // 4px for the indicator height (1px) + spacing
// Only update if values actually changed // Only update if values actually changed
if ( if (
lastStyleRef.current.width !== newWidth || lastStyleRef.current.width !== newWidth ||
lastStyleRef.current.left !== newLeft lastStyleRef.current.left !== newLeft ||
lastStyleRef.current.top !== newTop
) { ) {
isUpdatingRef.current = true isUpdatingRef.current = true
lastStyleRef.current = { width: newWidth, left: newLeft } lastStyleRef.current = { width: newWidth, left: newLeft, top: newTop }
setIndicatorStyle({ width: newWidth, left: newLeft }) setIndicatorStyle({ width: newWidth, left: newLeft, top: newTop })
// Reset flag after state update completes // Reset flag after state update completes
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -70,7 +82,7 @@ export default function Tabs({
}, [updateIndicatorPosition]) }, [updateIndicatorPosition])
useEffect(() => { useEffect(() => {
if (!containerRef.current) return if (!containerRef.current || !tabsContainerRef.current) return
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -97,6 +109,10 @@ export default function Tabs({
if (tab) resizeObserver.observe(tab) if (tab) resizeObserver.observe(tab)
}) })
if (tabsContainerRef.current) {
resizeObserver.observe(tabsContainerRef.current)
}
return () => { return () => {
resizeObserver.disconnect() resizeObserver.disconnect()
intersectionObserver.disconnect() intersectionObserver.disconnect()
@ -112,7 +128,7 @@ export default function Tabs({
)} )}
> >
<div className="flex-1 w-0"> <div className="flex-1 w-0">
<div className="flex flex-wrap relative gap-1"> <div ref={tabsContainerRef} className="flex flex-wrap relative gap-1">
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<div <div
key={tab.value} key={tab.value}
@ -129,10 +145,11 @@ export default function Tabs({
</div> </div>
))} ))}
<div <div
className="absolute bottom-0 h-1 bg-primary rounded-full transition-all duration-500" className="absolute h-1 bg-primary rounded-full transition-all duration-500"
style={{ style={{
width: `${indicatorStyle.width}px`, width: `${indicatorStyle.width}px`,
left: `${indicatorStyle.left}px` left: `${indicatorStyle.left}px`,
top: `${indicatorStyle.top}px`
}} }}
/> />
</div> </div>

Loading…
Cancel
Save