forked from koshikraj/ottopus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtab-bar.tsx
More file actions
108 lines (99 loc) · 3.21 KB
/
Copy pathtab-bar.tsx
File metadata and controls
108 lines (99 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
'use client'
import Link from 'next/link'
import { usePathname, useSearchParams } from 'next/navigation'
import { Suspense, type ReactNode } from 'react'
import { cn } from '@/lib/cn'
export interface Tab {
/** Value of the `tab` query parameter this selects. */
value: string
label: string
/** Present but not yet built. Renders inert rather than linking nowhere. */
disabled?: boolean
}
export interface TabBarProps {
tabs: readonly Tab[]
/** Used when the URL carries no `tab`. Defaults to the first tab. */
fallback?: string
/** Pushed to the right — the design puts a filter chip here. */
aside?: ReactNode
label: string
className?: string
}
/**
* The tab strip under a page header.
*
* Tabs are links carrying a `tab` query parameter, not local state: a person
* should be able to reload the activity page, or send someone the approvals
* view, and land where they were. That also means the active tab survives the
* back button, which local state does not.
*
* The Suspense boundary lives here rather than in every page. Reading the query
* string opts a route out of static prerendering, and the boundary is what
* keeps the rest of the page static — so the component that causes the problem
* is the one that solves it, and no caller has to remember.
*/
export function TabBar(props: TabBarProps) {
const initial = props.fallback ?? props.tabs[0]?.value
return (
<Suspense fallback={<Bar {...props} current={initial} />}>
<FromQuery {...props} />
</Suspense>
)
}
function FromQuery(props: TabBarProps) {
const params = useSearchParams()
const current = params.get('tab') ?? props.fallback ?? props.tabs[0]?.value
return <Bar {...props} current={current} />
}
function Bar({
tabs,
aside,
label,
className,
current,
}: TabBarProps & { current?: string }) {
const pathname = usePathname()
return (
<div
className={cn(
'flex flex-wrap items-center justify-between gap-4',
'border-b border-[var(--ot-border)] px-5 pt-4 sm:px-[26px]',
className,
)}
>
<nav aria-label={label} className="flex gap-5 overflow-x-auto">
{tabs.map((tab) => {
if (tab.disabled) {
return (
<span
key={tab.value}
aria-disabled
className="pb-3 text-[14px] font-medium whitespace-nowrap text-[var(--ot-text-4)]"
>
{tab.label}
</span>
)
}
const active = tab.value === current
return (
<Link
key={tab.value}
href={`${pathname}?tab=${tab.value}`}
aria-current={active ? 'page' : undefined}
className={cn(
'border-b-2 pb-3 text-[14px] font-semibold whitespace-nowrap',
'transition-colors duration-[var(--ot-dur-fast)]',
active
? 'border-[var(--ot-text)] text-[var(--ot-text)]'
: 'border-transparent text-[var(--ot-text-2)] hover:text-[var(--ot-text)]',
)}
>
{tab.label}
</Link>
)
})}
</nav>
{aside ? <div className="pb-[10px]">{aside}</div> : null}
</div>
)
}