5 changed files with 82 additions and 72 deletions
@ -1,66 +0,0 @@
@@ -1,66 +0,0 @@
|
||||
import { cn } from '@/lib/utils' |
||||
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider' |
||||
import { useTranslation } from 'react-i18next' |
||||
|
||||
type TabDefinition = { |
||||
value: string |
||||
label: string |
||||
onClick?: () => void |
||||
} |
||||
|
||||
export default function TabSwitcher({ |
||||
tabs, |
||||
value, |
||||
className, |
||||
onTabChange, |
||||
threshold = 800 |
||||
}: { |
||||
tabs: TabDefinition[] |
||||
value: string |
||||
className?: string |
||||
onTabChange?: (tab: string) => void |
||||
threshold?: number |
||||
}) { |
||||
const { t } = useTranslation() |
||||
const { deepBrowsing, lastScrollTop } = useDeepBrowsing() |
||||
const activeIndex = tabs.findIndex((tab) => tab.value === value) |
||||
|
||||
return ( |
||||
<div |
||||
className={cn( |
||||
'sticky top-12 bg-background z-30 w-full transition-transform', |
||||
deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : '', |
||||
className |
||||
)} |
||||
> |
||||
<div className="flex"> |
||||
{tabs.map((tab) => ( |
||||
<div |
||||
key={tab.value} |
||||
className={cn( |
||||
`flex-1 text-center py-2 font-semibold clickable cursor-pointer rounded-lg`, |
||||
value === tab.value ? '' : 'text-muted-foreground' |
||||
)} |
||||
onClick={() => { |
||||
tab.onClick?.() |
||||
onTabChange?.(tab.value) |
||||
}} |
||||
> |
||||
{t(tab.label)} |
||||
</div> |
||||
))} |
||||
</div> |
||||
<div className="relative"> |
||||
<div |
||||
className="absolute bottom-0 px-4 transition-all duration-500" |
||||
style={{ |
||||
width: `${100 / tabs.length}%`, |
||||
left: `${activeIndex >= 0 ? activeIndex * (100 / tabs.length) : 0}%` |
||||
}} |
||||
> |
||||
<div className="w-full h-1 bg-primary rounded-full" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
import { cn } from '@/lib/utils' |
||||
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider' |
||||
import { useEffect, useRef, useState } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
|
||||
type TabDefinition = { |
||||
value: string |
||||
label: string |
||||
} |
||||
|
||||
export default function Tabs({ |
||||
tabs, |
||||
value, |
||||
onTabChange, |
||||
threshold = 800 |
||||
}: { |
||||
tabs: TabDefinition[] |
||||
value: string |
||||
onTabChange?: (tab: string) => void |
||||
threshold?: number |
||||
}) { |
||||
const { t } = useTranslation() |
||||
const { deepBrowsing, lastScrollTop } = useDeepBrowsing() |
||||
const activeIndex = tabs.findIndex((tab) => tab.value === value) |
||||
const tabRefs = useRef<(HTMLDivElement | null)[]>([]) |
||||
const [indicatorStyle, setIndicatorStyle] = useState({ width: 0, left: 0 }) |
||||
|
||||
useEffect(() => { |
||||
const handleResize = () => { |
||||
if (activeIndex >= 0 && tabRefs.current[activeIndex]) { |
||||
const activeTab = tabRefs.current[activeIndex] |
||||
const { offsetWidth, offsetLeft } = activeTab |
||||
const padding = 32 // 16px padding on each side
|
||||
setIndicatorStyle({ |
||||
width: offsetWidth - padding, |
||||
left: offsetLeft + padding / 2 |
||||
}) |
||||
} |
||||
} |
||||
window.addEventListener('resize', handleResize) |
||||
handleResize() // Initial call to set the indicator style
|
||||
return () => { |
||||
window.removeEventListener('resize', handleResize) |
||||
} |
||||
}, [activeIndex]) |
||||
|
||||
return ( |
||||
<div |
||||
className={cn( |
||||
'sticky flex justify-around top-12 p-1 bg-background z-30 w-full transition-transform', |
||||
deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : '' |
||||
)} |
||||
> |
||||
{tabs.map((tab, index) => ( |
||||
<div |
||||
key={tab.value} |
||||
ref={(el) => (tabRefs.current[index] = el)} |
||||
className={cn( |
||||
`text-center w-full py-2 font-semibold clickable cursor-pointer rounded-lg`, |
||||
value === tab.value ? '' : 'text-muted-foreground' |
||||
)} |
||||
onClick={() => onTabChange?.(tab.value)} |
||||
> |
||||
{t(tab.label)} |
||||
</div> |
||||
))} |
||||
<div |
||||
className="absolute bottom-0 h-1 bg-primary rounded-full transition-all duration-500" |
||||
style={{ |
||||
width: `${indicatorStyle.width}px`, |
||||
left: `${indicatorStyle.left}px` |
||||
}} |
||||
/> |
||||
</div> |
||||
) |
||||
} |
||||
Loading…
Reference in new issue