Fortura/apps/www/public/r/headless-tabs.json
2025-08-20 04:12:49 -06:00

22 lines
6.4 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "headless-tabs",
"type": "registry:ui",
"title": "Headless Tabs",
"description": "Easily create accessible, fully customizable tab interfaces, with robust focus management and keyboard navigation support.",
"dependencies": [
"motion",
"@headlessui/react"
],
"registryDependencies": [
"https://animate-ui.com/r/motion-highlight"
],
"files": [
{
"path": "registry/headless/tabs/index.tsx",
"content": "'use client';\n\nimport * as React from 'react';\nimport { motion, type Transition } from 'motion/react';\nimport {\n TabGroup as TabGroupPrimitive,\n TabList as TabListPrimitive,\n Tab as TabPrimitive,\n TabPanels as TabPanelsPrimitive,\n TabPanel as TabPanelPrimitive,\n type TabGroupProps as TabGroupPrimitiveProps,\n type TabListProps as TabListPrimitiveProps,\n type TabProps as TabPrimitiveProps,\n type TabPanelsProps as TabPanelsPrimitiveProps,\n type TabPanelProps as TabPanelPrimitiveProps,\n} from '@headlessui/react';\n\nimport { cn } from '@/lib/utils';\nimport {\n MotionHighlight,\n MotionHighlightItem,\n} from '@/components/animate-ui/effects/motion-highlight';\n\ntype TabGroupProps<TTag extends React.ElementType = 'div'> =\n TabGroupPrimitiveProps<TTag> & {\n className?: string;\n as?: TTag;\n };\n\nfunction TabGroup<TTag extends React.ElementType = 'div'>({\n className,\n ...props\n}: TabGroupProps<TTag>) {\n return (\n <TabGroupPrimitive\n data-slot=\"tab-group\"\n className={cn('flex flex-col gap-2', className)}\n {...props}\n />\n );\n}\n\ntype TabListProps<TTag extends React.ElementType = 'div'> =\n TabListPrimitiveProps<TTag> & {\n as?: TTag;\n className?: string;\n activeClassName?: string;\n transition?: Transition;\n };\n\nfunction TabList<TTag extends React.ElementType = 'div'>({\n children,\n className,\n activeClassName,\n transition = {\n type: 'spring',\n stiffness: 200,\n damping: 25,\n },\n ...props\n}: TabListProps<TTag>) {\n return (\n <TabListPrimitive\n data-slot=\"tab-list\"\n className={cn(\n 'bg-muted text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg p-[4px]',\n className,\n )}\n {...props}\n >\n {(bag) => (\n <MotionHighlight\n controlledItems\n className={cn('rounded-sm bg-background shadow-sm', activeClassName)}\n value={bag.selectedIndex.toString()}\n transition={transition}\n >\n {typeof children === 'function' ? children(bag) : children}\n </MotionHighlight>\n )}\n </TabListPrimitive>\n );\n}\n\ntype TabProps<TTag extends React.ElementType = 'button'> = Omit<\n TabPrimitiveProps<TTag>,\n 'children'\n> &\n Required<Pick<TabPrimitiveProps<TTag>, 'children'>> & {\n index: number;\n className?: string;\n as?: TTag;\n };\n\nfunction Tab<TTag extends React.ElementType = 'button'>(props: TabProps<TTag>) {\n const { children, className, index, as = 'button', ...rest } = props;\n\n return (\n <MotionHighlightItem value={index.toString()} className=\"size-full\">\n <TabPrimitive\n data-slot=\"tabs-trigger\"\n className={cn(\n 'inline-flex cursor-pointer items-center size-full justify-center whitespace-nowrap rounded-sm px-2 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-selected:text-foreground z-[1]',\n className,\n )}\n as={as as React.ElementType}\n {...rest}\n >\n {children}\n </TabPrimitive>\n </MotionHighlightItem>\n );\n}\n\ntype TabPanelProps<TTag extends React.ElementType = typeof motion.div> = Omit<\n TabPanelPrimitiveProps<TTag>,\n 'transition'\n> & {\n children: React.ReactNode;\n className?: string;\n as?: TTag;\n transition?: Transition;\n};\n\nfunction TabPanel<TTag extends React.ElementType = typeof motion.div>(\n props: TabPanelProps<TTag>,\n) {\n const {\n className,\n as = motion.div,\n transition = {\n duration: 0.5,\n ease: 'easeInOut',\n },\n ...rest\n } = props;\n\n return (\n <TabPanelPrimitive\n data-slot=\"tabs-content\"\n className={cn('flex-1 outline-none', className)}\n layout\n initial={{ opacity: 0, y: -10, filter: 'blur(4px)' }}\n animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}\n exit={{ opacity: 0, y: 10, filter: 'blur(4px)' }}\n transition={transition}\n as={as as React.ElementType}\n {...rest}\n />\n );\n}\n\ntype TabPanelsProps<TTag extends React.ElementType = typeof motion.div> = Omit<\n TabPanelsPrimitiveProps<TTag>,\n 'transition'\n> & {\n className?: string;\n as?: TTag;\n transition?: Transition;\n};\n\nfunction TabPanels<TTag extends React.ElementType = typeof motion.div>(\n props: TabPanelsProps<TTag>,\n) {\n const {\n children,\n className,\n as = motion.div,\n transition = { type: 'spring', stiffness: 200, damping: 25 },\n ...rest\n } = props;\n const containerRef = React.useRef<HTMLDivElement | null>(null);\n\n const [height, setHeight] = React.useState(0);\n\n React.useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n const newHeight = entries[0]?.contentRect.height ?? 0;\n requestAnimationFrame(() => {\n setHeight(newHeight);\n });\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [children]);\n\n React.useLayoutEffect(() => {\n if (containerRef.current) {\n const initialHeight = containerRef.current.getBoundingClientRect().height;\n setHeight(initialHeight);\n }\n }, [children]);\n\n return (\n <TabPanelsPrimitive\n data-slot=\"tabs-contents\"\n layout\n animate={{ height: height }}\n transition={transition}\n as={as as React.ElementType}\n className={className}\n {...rest}\n >\n <div ref={containerRef}>{children}</div>\n </TabPanelsPrimitive>\n );\n}\n\nexport {\n TabGroup,\n TabList,\n Tab,\n TabPanel,\n TabPanels,\n type TabGroupProps,\n type TabListProps,\n type TabProps,\n type TabPanelProps,\n type TabPanelsProps,\n};\n",
"type": "registry:ui",
"target": "components/animate-ui/headless/tabs.tsx"
}
]
}