Files
Terry SuttonandIvan Vasilov 50ede5546f Chore/webhook json (#30797)
* Enforce json payloads with webhook jobs

* EFs need json as well

* Handle case where the body can be null/undefined.

* Fix url in the child id.

* Fix a redirect for cron.

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2024-12-03 09:05:56 -03:30

115 lines
4.5 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 { useParams } from 'common'
import { INTEGRATIONS } from 'components/interfaces/Integrations/Landing/Integrations.constants'
import { useInstalledIntegrations } from 'components/interfaces/Integrations/Landing/useInstalledIntegrations'
import { 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 { 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="px-10 [&_ul]:items-center bg-200"
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'