refactor: Tabs component
This commit is contained in:
@@ -18,7 +18,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import PullToRefresh from 'react-simple-pull-to-refresh'
|
import PullToRefresh from 'react-simple-pull-to-refresh'
|
||||||
import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
|
import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
|
||||||
import { PictureNoteCardMasonry } from '../PictureNoteCardMasonry'
|
import { PictureNoteCardMasonry } from '../PictureNoteCardMasonry'
|
||||||
import TabSwitcher from '../TabSwitch'
|
import Tabs from '../Tabs'
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = 100
|
||||||
const ALGO_LIMIT = 500
|
const ALGO_LIMIT = 500
|
||||||
@@ -291,7 +291,7 @@ export default function NoteList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<TabSwitcher
|
<Tabs
|
||||||
value={listMode}
|
value={listMode}
|
||||||
tabs={
|
tabs={
|
||||||
pubkey && author && pubkey !== author
|
pubkey && author && pubkey !== author
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { Event, kinds } from 'nostr-tools'
|
|||||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import PullToRefresh from 'react-simple-pull-to-refresh'
|
import PullToRefresh from 'react-simple-pull-to-refresh'
|
||||||
import TabSwitcher from '../TabSwitch'
|
import Tabs from '../Tabs'
|
||||||
import { NotificationItem } from './NotificationItem'
|
import { NotificationItem } from './NotificationItem'
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = 100
|
||||||
@@ -195,7 +195,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TabSwitcher
|
<Tabs
|
||||||
value={notificationType}
|
value={notificationType}
|
||||||
tabs={[
|
tabs={[
|
||||||
{ value: 'all', label: 'All' },
|
{ value: 'all', label: 'All' },
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
76
src/components/Tabs/index.tsx
Normal file
76
src/components/Tabs/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import FollowingFavoriteRelayList from '@/components/FollowingFavoriteRelayList'
|
import FollowingFavoriteRelayList from '@/components/FollowingFavoriteRelayList'
|
||||||
import RelayList from '@/components/RelayList'
|
import RelayList from '@/components/RelayList'
|
||||||
import TabSwitcher from '@/components/TabSwitch'
|
import Tabs from '@/components/Tabs'
|
||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
import { Compass } from 'lucide-react'
|
import { Compass } from 'lucide-react'
|
||||||
import { forwardRef, useState } from 'react'
|
import { forwardRef, useState } from 'react'
|
||||||
@@ -18,7 +18,7 @@ const ExplorePage = forwardRef((_, ref) => {
|
|||||||
titlebar={<ExplorePageTitlebar />}
|
titlebar={<ExplorePageTitlebar />}
|
||||||
displayScrollToTopButton
|
displayScrollToTopButton
|
||||||
>
|
>
|
||||||
<TabSwitcher
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
tabs={[
|
tabs={[
|
||||||
{ value: 'following', label: "Following's Favorites" },
|
{ value: 'following', label: "Following's Favorites" },
|
||||||
|
|||||||
Reference in New Issue
Block a user