refactor: Tabs component

This commit is contained in:
codytseng
2025-07-02 22:52:04 +08:00
parent 2489e8098d
commit 00bb3c712b
5 changed files with 82 additions and 72 deletions

View File

@@ -18,7 +18,7 @@ import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh'
import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
import { PictureNoteCardMasonry } from '../PictureNoteCardMasonry'
import TabSwitcher from '../TabSwitch'
import Tabs from '../Tabs'
const LIMIT = 100
const ALGO_LIMIT = 500
@@ -291,7 +291,7 @@ export default function NoteList({
return (
<div className={className}>
<TabSwitcher
<Tabs
value={listMode}
tabs={
pubkey && author && pubkey !== author

View File

@@ -13,7 +13,7 @@ import { Event, kinds } from 'nostr-tools'
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh'
import TabSwitcher from '../TabSwitch'
import Tabs from '../Tabs'
import { NotificationItem } from './NotificationItem'
const LIMIT = 100
@@ -195,7 +195,7 @@ const NotificationList = forwardRef((_, ref) => {
return (
<div>
<TabSwitcher
<Tabs
value={notificationType}
tabs={[
{ value: 'all', label: 'All' },

View File

@@ -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>
)
}

View File

@@ -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>
)
}