mirror of
https://github.com/supabase/supabase.git
synced 2026-10-16 06:39:09 +03:00
* improve some studio mobile layouts * improve some studio mobile layouts * improve settings * assistant mobile * assistant mobile * assistant mobile * responsive formlayout and new project layout * improve dashboard pages headers * improve dashboard auth pages * mobile org settings * mobile billing fixes * adjust paused project container height * remove comments * triggers * leftovers * ai assistant * fix errors * remove 16px input size * fix test * merge access tokens settings page conflicsts * smol integrations here and there
116 lines
4.6 KiB
TypeScript
116 lines
4.6 KiB
TypeScript
import { AnimatePresence, motion, MotionProps, useScroll, useTransform } from 'framer-motion'
|
|
import { ChevronRight } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { ComponentProps, ComponentType, useRef } from 'react'
|
|
|
|
import { useBreakpoint, useParams } from 'common'
|
|
import { INTEGRATIONS } from 'components/interfaces/Integrations/Landing/Integrations.constants'
|
|
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
|
|
import { cn, NavMenu, NavMenuItem } from 'ui'
|
|
import { useProjectContext } from '../ProjectLayout/ProjectContext'
|
|
|
|
const MotionNavMenu = motion(NavMenu) as ComponentType<ComponentProps<typeof NavMenu> & MotionProps>
|
|
|
|
// Output range: The padding range for the nav (from compact to expanded)
|
|
const paddingRange = [40, 86]
|
|
|
|
// Output range: The padding range for the image
|
|
const iconPaddingRange = [3, 1.5] // From 1.5px (scrolled) to 4px (top)
|
|
|
|
interface IntegrationTabsProps {
|
|
scroll: ReturnType<typeof useScroll>
|
|
isSticky?: boolean
|
|
}
|
|
|
|
export const IntegrationTabs = ({ scroll, isSticky }: IntegrationTabsProps) => {
|
|
const navRef = useRef(null)
|
|
const { project } = useProjectContext()
|
|
const { id, pageId, childId } = useParams()
|
|
const isMobile = useBreakpoint('md')
|
|
|
|
const { installedIntegrations } = useInstalledIntegrations()
|
|
// Find the integration details based on ID
|
|
const integration = INTEGRATIONS.find((i) => i.id === id)
|
|
|
|
const headerRef = useRef<HTMLDivElement>(null)
|
|
|
|
// Input range: The scrollY range for triggering the animation (e.g., 0 to 200px of scroll)
|
|
const scrollRange = [40, headerRef.current?.offsetHeight ?? 128]
|
|
const navInnerLeftPaddingX = useTransform(scroll?.scrollY!, scrollRange, paddingRange)
|
|
const iconPadding = useTransform(scroll?.scrollY!, scrollRange, iconPaddingRange)
|
|
|
|
const installedIntegration = installedIntegrations?.find((i) => i.id === id)
|
|
|
|
const tabs = installedIntegration
|
|
? integration?.navigation ?? []
|
|
: (integration?.navigation ?? []).filter((tab) => tab.route === 'overview')
|
|
|
|
if (!integration) return null
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
<div className="sticky top-[0px] z-[1] bg-dash-sidebar" ref={navRef}>
|
|
<MotionNavMenu
|
|
transition={{ duration: 0.2 }}
|
|
className={cn('px-4 md:px-10 [&_ul]:items-center bg-200', isMobile && '!px-4')}
|
|
aria-label="Integration menu"
|
|
style={{ paddingLeft: !isSticky ? (navInnerLeftPaddingX as unknown as number) : 40 }}
|
|
>
|
|
{isSticky && (
|
|
<motion.div
|
|
layoutId="integration-icon"
|
|
className="w-[20px] h-[20px] relative bg-white rounded"
|
|
transition={{ duration: 0 }}
|
|
>
|
|
{integration?.icon({
|
|
style: { padding: iconPadding.get() },
|
|
})}
|
|
</motion.div>
|
|
)}
|
|
|
|
{tabs.map((tab) => {
|
|
const tabUrl = `/project/${project?.ref}/integrations/${integration?.id}/${tab.route}`
|
|
return (
|
|
<div className="flex items-center gap-2" key={tab.route}>
|
|
<NavMenuItem active={pageId === tab.route && !childId}>
|
|
<Link href={tabUrl}>{tab.label}</Link>
|
|
</NavMenuItem>
|
|
|
|
<AnimatePresence>
|
|
{tab.hasChild && childId && (
|
|
<>
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -10 }}
|
|
transition={{ duration: 0.12, delay: 0.05 }}
|
|
className="flex items-center"
|
|
>
|
|
<ChevronRight size={14} className="text-foreground-muted" />
|
|
</motion.div>
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -10 }}
|
|
transition={{ duration: 0.12, delay: 0.1 }}
|
|
className="flex items-center"
|
|
>
|
|
<NavMenuItem active={true} className="flex items-center gap-2">
|
|
{tab.childIcon}
|
|
<Link href={`${tabUrl}/${childId}`}>{childId}</Link>
|
|
</NavMenuItem>
|
|
</motion.div>
|
|
</>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
)
|
|
})}
|
|
</MotionNavMenu>
|
|
</div>
|
|
</AnimatePresence>
|
|
)
|
|
}
|
|
|
|
IntegrationTabs.displayName = 'IntegrationTabs'
|