2022-03-31 06:24:35 +00:00
|
|
|
import clsx from 'clsx'
|
2022-07-25 20:27:09 +00:00
|
|
|
import { useRouter, NextRouter } from 'next/router'
|
2022-05-26 21:41:24 +00:00
|
|
|
import { ReactNode, useState } from 'react'
|
2022-07-07 21:24:13 +00:00
|
|
|
import { track } from '@amplitude/analytics-browser'
|
2022-03-31 06:24:35 +00:00
|
|
|
|
|
|
|
type Tab = {
|
|
|
|
title: string
|
2022-05-26 21:41:24 +00:00
|
|
|
tabIcon?: ReactNode
|
|
|
|
content: ReactNode
|
2022-07-24 07:11:33 +00:00
|
|
|
// If set, show a badge with this content
|
|
|
|
badge?: string
|
2022-03-31 06:24:35 +00:00
|
|
|
}
|
|
|
|
|
2022-07-25 20:27:09 +00:00
|
|
|
type TabProps = {
|
2022-05-05 22:30:30 +00:00
|
|
|
tabs: Tab[]
|
2022-07-01 22:37:30 +00:00
|
|
|
labelClassName?: string
|
2022-06-06 19:53:02 +00:00
|
|
|
onClick?: (tabTitle: string, index: number) => void
|
2022-07-07 12:53:14 +00:00
|
|
|
className?: string
|
2022-07-07 21:24:13 +00:00
|
|
|
currentPageForAnalytics?: string
|
2022-07-25 20:27:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function ControlledTabs(props: TabProps & { activeIndex: number }) {
|
2022-07-07 21:24:13 +00:00
|
|
|
const {
|
|
|
|
tabs,
|
2022-07-25 20:27:09 +00:00
|
|
|
activeIndex,
|
2022-07-07 21:24:13 +00:00
|
|
|
labelClassName,
|
|
|
|
onClick,
|
|
|
|
className,
|
|
|
|
currentPageForAnalytics,
|
|
|
|
} = props
|
2022-03-31 06:24:35 +00:00
|
|
|
return (
|
2022-07-01 22:37:30 +00:00
|
|
|
<>
|
2022-07-25 20:27:09 +00:00
|
|
|
<nav
|
2022-09-22 16:12:53 +00:00
|
|
|
className={clsx(' space-x-8 border-b border-gray-200', className)}
|
2022-07-25 20:27:09 +00:00
|
|
|
aria-label="Tabs"
|
|
|
|
>
|
|
|
|
{tabs.map((tab, i) => (
|
|
|
|
<a
|
|
|
|
href="#"
|
|
|
|
key={tab.title}
|
|
|
|
onClick={(e) => {
|
|
|
|
e.preventDefault()
|
|
|
|
track('Clicked Tab', {
|
|
|
|
title: tab.title,
|
|
|
|
currentPage: currentPageForAnalytics,
|
|
|
|
})
|
|
|
|
onClick?.(tab.title, i)
|
|
|
|
}}
|
|
|
|
className={clsx(
|
|
|
|
activeIndex === i
|
|
|
|
? 'border-indigo-500 text-indigo-600'
|
|
|
|
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
|
|
|
|
'inline-flex cursor-pointer flex-row gap-1 whitespace-nowrap border-b-2 px-1 py-3 text-sm font-medium',
|
|
|
|
labelClassName
|
|
|
|
)}
|
|
|
|
aria-current={activeIndex === i ? 'page' : undefined}
|
|
|
|
>
|
|
|
|
{tab.tabIcon && <span>{tab.tabIcon}</span>}
|
|
|
|
{tab.badge ? (
|
|
|
|
<span className="px-0.5 font-bold">{tab.badge}</span>
|
|
|
|
) : null}
|
|
|
|
{tab.title}
|
|
|
|
</a>
|
|
|
|
))}
|
|
|
|
</nav>
|
2022-08-11 19:53:42 +00:00
|
|
|
{tabs.map((tab, i) => (
|
|
|
|
<div key={i} className={i === activeIndex ? 'block' : 'hidden'}>
|
|
|
|
{tab.content}
|
|
|
|
</div>
|
|
|
|
))}
|
2022-07-01 22:37:30 +00:00
|
|
|
</>
|
2022-03-31 06:24:35 +00:00
|
|
|
)
|
|
|
|
}
|
2022-07-25 20:27:09 +00:00
|
|
|
|
|
|
|
export function UncontrolledTabs(props: TabProps & { defaultIndex?: number }) {
|
|
|
|
const { defaultIndex, onClick, ...rest } = props
|
|
|
|
const [activeIndex, setActiveIndex] = useState(defaultIndex ?? 0)
|
|
|
|
return (
|
|
|
|
<ControlledTabs
|
|
|
|
{...rest}
|
|
|
|
activeIndex={activeIndex}
|
|
|
|
onClick={(title, i) => {
|
|
|
|
setActiveIndex(i)
|
|
|
|
onClick?.(title, i)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const isTabSelected = (router: NextRouter, queryParam: string, tab: Tab) => {
|
|
|
|
const selected = router.query[queryParam]
|
|
|
|
if (typeof selected === 'string') {
|
|
|
|
return tab.title.toLowerCase() === selected
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function QueryUncontrolledTabs(
|
|
|
|
props: TabProps & { defaultIndex?: number }
|
|
|
|
) {
|
|
|
|
const { tabs, defaultIndex, onClick, ...rest } = props
|
|
|
|
const router = useRouter()
|
|
|
|
const selectedIdx = tabs.findIndex((t) => isTabSelected(router, 'tab', t))
|
|
|
|
const activeIndex = selectedIdx !== -1 ? selectedIdx : defaultIndex ?? 0
|
|
|
|
return (
|
|
|
|
<ControlledTabs
|
|
|
|
{...rest}
|
|
|
|
tabs={tabs}
|
|
|
|
activeIndex={activeIndex}
|
|
|
|
onClick={(title, i) => {
|
|
|
|
router.replace(
|
|
|
|
{ query: { ...router.query, tab: title.toLowerCase() } },
|
|
|
|
undefined,
|
|
|
|
{ shallow: true }
|
|
|
|
)
|
|
|
|
onClick?.(title, i)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// legacy code that didn't know about any other kind of tabs imports this
|
|
|
|
export const Tabs = UncontrolledTabs
|