1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9211],{374:function(n,e,t){t.d(e,{B:function(){return Demo}});var s=t(1527),a=t(1955),r=t(3716),i=t(4875),o=t.n(i),l=t(1315),c=t(959),d=t(8584);let u=(0,c.forwardRef)(function(n,e){let{className:t,...a}=n;return(0,s.jsx)(l.E.button,{ref:e,...a,className:o()(t,"cursor-pointer origin-center flex items-center justify-center select-none flex-shrink-0 bg-transparent")})});function Editor(n){let{className:e,isOpen:t,...r}=n;return(0,s.jsx)("div",{className:o()(t?"h-auto":"h-80","flex flex-col w-full border-1 border-t-0 border-zinc-100 rounded-none","[&>.sp-stack>.sp-tabs]:border-b-1","[&>.sp-stack>.sp-code-editor]:rounded-none","[&>.sp-stack]:h-full [&>div]:grow [&>div]:rounded-none",e),...r,children:(0,s.jsx)(a._V,{})})}function Preview(n){let{className:e,children:t,...r}=n;return(0,s.jsx)(s.Fragment,{children:(0,s.jsx)("div",{className:o()("flex flex-col z-0 relative rounded-none",e),...r,children:(0,s.jsx)(a.Gj,{suppressHydrationWarning:!0,className:o()("h-full border-1 rounded-t-none border-zinc-100 [&_.sp-sides_*]:border-zinc-100","[&>.sp-preview-container]:rounded-none","[&_.sp-cube-wrapper]:opacity-0"),showOpenInCodeSandbox:!1,showRestartButton:!1,showRefreshButton:!1,children:(0,s.jsx)(l.E.div,{className:"absolute bottom-2 right-2 flex space-x-3 items-center justify-end p-1 z-10",children:t})})})})}var m=t(3692),b=t(3944);function RefreshButton(){let{refresh:n}=(0,a.xN)(),e=(0,a.QE)(),t=(0,m.c)(0),r=(0,c.useCallback)(()=>{n()},[n]);return(0,c.useEffect)(()=>{"LOADING"===e?(0,b.j)(t,t.get()-360,{repeat:1/0,ease:"linear",duration:.5}):(0,b.j)(t,t.get(),{duration:0})},[e,t]),(0,s.jsx)(u,{onClick:r,style:{rotate:t},children:(0,s.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor",className:"w-4 h-4 -scale-x-100 scale-y-100 text-black",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"})})})}var f=t(60);function Demo(n){var e,t;let{files:i,className:m,previewHeightClassname:b,hideEditor:p,options:x,...h}=n,[v,g]=(0,c.useState)(!1),T=f.a4;return(0,s.jsx)(a.oT,{template:"react-ts",files:i,theme:T,options:{...x,externalResources:["https://cdn.tailwindcss.com",...null!==(e=null==x?void 0:x.externalResources)&&void 0!==e?e:[]],autorun:!0},...h,children:(0,s.jsxs)("div",{className:o()(m,"flex flex-col items-center justify-center space-y-2 not-prose","article-content-lg my-4 md:my-12"),children:[(0,s.jsxs)("div",{className:"flex flex-col w-full mb-2",children:[(0,s.jsxs)(Preview,{className:o()(b),children:[(0,s.jsxs)(r.fC,{children:[(0,s.jsx)(r.xz,{asChild:!0,children:(0,s.jsx)(u,{className:"text-black",children:(0,s.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor",className:"w-4 h-4 -scale-x-100 scale-y-100",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M2
10.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"})})})}),(0,s.jsxs)(r.h_,{children:[(0,s.jsx)(r.aV,{className:"bg-foreground/40 dark:bg-background/50 fixed inset-0 z-30"}),(0,s.jsx)(a.oT,{template:"react-ts",files:i,theme:T,options:{...x,externalResources:["https://cdn.tailwindcss.com",...null!==(t=null==x?void 0:x.externalResources)&&void 0!==t?t:[]],autorun:!0},...h,children:(0,s.jsx)(r.VY,{className:"z-40 shadow-md bg-white inset-8 max-w-5xl mx-auto fixed rounded-none",children:(0,s.jsx)(a.Gj,{suppressHydrationWarning:!0,className:o()("z-0 relative h-full border-2 rounded-none [&_.sp-sides_*]:border-black","[&>.sp-preview-container]:rounded-none","[&_.sp-cube-wrapper]:opacity-0"),showOpenInCodeSandbox:!1,showRestartButton:!1,showRefreshButton:!1,children:(0,s.jsxs)(l.E.div,{className:"absolute bottom-2 right-2 flex space-x-3 items-center justify-end p-1 z-10",children:[(0,s.jsx)(r.x8,{asChild:!0,children:(0,s.jsx)(u,{"aria-label":"Close",children:(0,s.jsx)(u,{className:"text-black",children:(0,s.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor",className:"w-4 h-4",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"})})})})}),(0,s.jsx)(RefreshButton,{})]})})})})]})]}),(0,s.jsx)(RefreshButton,{})]}),!p&&(0,s.jsx)(Editor,{isOpen:v})]}),!p&&(0,s.jsx)(d.z,{size:"sm",onClick:()=>g(n=>!n),className:"mt-1",children:v?"show less":"show more"})]})})}},60:function(n,e,t){t.d(e,{a4:function(){return s},aI:function(){return a}});let s={colors:{surface1:"#ffffff",surface2:"#F3F3F3",surface3:"#f5f5f5",clickable:"#959da5",base:"#24292e",disabled:"#d1d4d8",hover:"#24292e",accent:"#24292e"},syntax:{keyword:"#666666",property:{color:"#121212"},plain:"#24292e",static:"#121212",string:"#121212",definition:"#666666",punctuation:"#24292e",tag:"#666666",comment:{color:"#6a737d",fontStyle:"normal"}},font:{body:'var(--font-dm-sans), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',mono:'var(--font-fira-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',size:"12px",lineHeight:"18px"}},a={colors:{surface1:"#1a1a1a",surface2:"#2a2a2a",surface3:"#333333",clickable:"#959da5",base:"#ffffff",disabled:"#666666",hover:"#ffffff",accent:"#ffffff"},syntax:{keyword:"#999999",property:{color:"#ffffff"},plain:"#ffffff",static:"#ffffff",string:"#ffffff",definition:"#999999",punctuation:"#ffffff",tag:"#999999",comment:{color:"#888888",fontStyle:"normal"}},font:{body:'var(--font-dm-sans), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',mono:'var(--font-fira-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',size:"12px",lineHeight:"18px"}}},9211:function(n,e,t){t.d(e,{k4:function(){return CSSExample},iE:function(){return FramerExample},_M:function(){return FramerLayoutExample},CG:function(){return SpringExample},L4:function(){return TabsDemo},K1:function(){return TransitionGroupExample}});var s=t(1527);t(959);var a=t(374);let r='import { useState } from "react";\nimport { Framer } from "./framer";\nimport { useTabs } from "./useTabs";\nimport { Circle, Square, Triangle } from "./shapes";\n\nexport default function App() {\n const [hookProps] = useState({\n tabs: [\n {\n label: "Circle",\n children: <Circle />,\n id: "Circle",\n },\n {\n label: "Triangle",\n children: <Triangle />,\n id: "Triangle",\n },\n {\n label: "Square",\n children: <Square />,\n id: "Square",\n },\n ],\n initialTabId: "Triangle",\n });\n const framer = useTabs(hookProps);\n return (\n <div className="w-full flex flex-col space-y-24 items-center justify-center">\n <div className="max-w-6xl">\n <Framer.Tabs {...framer.tabProps} />\n <Framer.Content\n {...framer.contentProps}
1\n className="text-center rounded-3xl py-9 flex flex-col items-center"\n >\n {framer.selectedTab.children}\n </Framer.Content>\n </div>\n </div>\n );\n}\n',i='import { ReactNode, useState } from "react";\n\nexport type Tab = { label: string; id: string; children: ReactNode };\n\nexport function useTabs({\n tabs,\n initialTabId,\n onChange,\n}: {\n tabs: Tab[];\n initialTabId: string;\n onChange?: (id: string) => void;\n}) {\n const [[selectedTabIndex, direction], setSelectedTab] = useState(() => {\n const indexOfInitialTab = tabs.findIndex((tab) => tab.id === initialTabId);\n return [indexOfInitialTab === -1 ? 0 : indexOfInitialTab, 0];\n });\n\n return {\n tabProps: {\n tabs,\n selectedTabIndex,\n onChange,\n setSelectedTab,\n },\n selectedTab: tabs[selectedTabIndex],\n contentProps: {\n direction,\n selectedTabIndex,\n },\n };\n}\n',o='import classNames from "classnames";\nimport React, { ReactNode, useEffect, useRef, useState } from "react";\nimport { AnimatePresence, motion } from "framer-motion";\n\nimport { Tab } from "./useTabs";\n\nconst transition = {\n type: "tween",\n ease: "easeOut",\n duration: 0.15,\n};\n\ntype Props = {\n selectedTabIndex: number;\n tabs: Tab[];\n setSelectedTab: (input: [number, number]) => void;\n};\n\nconst Tabs = ({\n tabs,\n selectedTabIndex,\n setSelectedTab,\n}: Props): JSX.Element => {\n const [buttonRefs, setButtonRefs] = useState<Array<HTMLButtonElement | null>>(\n []\n );\n\n useEffect(() => {\n setButtonRefs((prev) => prev.slice(0, tabs.length));\n }, [tabs.length]);\n\n const navRef = useRef<HTMLDivElement>(null);\n const navRect = navRef.current?.getBoundingClientRect();\n\n const selectedRect = buttonRefs[selectedTabIndex]?.getBoundingClientRect();\n\n const [hoveredTabIndex, setHoveredTabIndex] = useState<number | null>(null);\n const hoveredRect =\n buttonRefs[hoveredTabIndex ?? -1]?.getBoundingClientRect();\n\n return (\n <nav\n ref={navRef}\n className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"\n onPointerLeave={(e) => setHoveredTabIndex(null)}\n >\n {tabs.map((item, i) => {\n return (\n <motion.button\n key={i}\n className={classNames(\n "text-md relative rounded-md flex items-center h-8 px-4 z-20 bg-transparent text-sm text-slate-500 cursor-pointer select-none transition-colors",\n {\n "text-slate-700":\n hoveredTabIndex === i || selectedTabIndex === i,\n }\n )}\n ref={(el) => (buttonRefs[i] = el)}\n onPointerEnter={() => {\n setHoveredTabIndex(i);\n }}\n onFocus={() => {\n setHoveredTabIndex(i);\n }}\n onClick={() => {\n setSelectedTab([i, i > selectedTabIndex ? 1 : -1]);\n }}\n >\n {item.label}\n </motion.button>\n );\n })}\n <AnimatePresence>\n {hoveredRect && navRect && (\n <motion.div\n key={"hover"}\n className="absolute z-10 top-0 left-0 rounded-md bg-gray-200"\n initial={{\n x: hoveredRect.left - navRect.left,\n y: hoveredRect.top - navRect.top,\n width: hoveredRect.width,\n height: hoveredRect.height,\n opacity: 0,\n }}\n animate={{\n x: hoveredRect.left - navRect.left,\n y: hoveredRect.top - navRect.top,\n width: hoveredRect.width,\n height: hoveredRect.height,\n opacity: 1,\n }}\n exit={{\n x: hoveredRect.left - navRect.left,\n y: hoveredRect.top - navRect.top,\n width: hoveredRect.width,\n height: hoveredRect.height,\n opacity: 0,\n }}\n transition={transition}\n />\n )}\n </AnimatePresence>\n {selectedRect && navRect && (\n <motion.div\n className={"absolute z-10 bottom-0 left-0 h-[2px] bg-slate-500"}\n initial={false}\n animate={{\n width: selectedRect.width * 0.8,\n x: `calc(${selectedRect.left - navRect.left}px + 10%)`,\n opacity: 1,\n }}\n transition={transition}\n />\n )}\n </nav>\n );\n};\n\nconst Content = ({\n children,\n className,\n selectedTabIndex,\n direction,\n}: {\n direction: number;\n selectedTabIndex: number;\n children: ReactNode;\n className?: string;\n}): JSX.Element =>
1 {\n return (\n <AnimatePresence exitBeforeEnter={false} custom={direction}>\n <motion.div\n key={selectedTabIndex}\n variants={{\n enter: (direction) => ({\n opacity: 0,\n x: direction > 0 ? 100 : -100,\n scale: 0.8,\n }),\n center: { opacity: 1, x: 0, scale: 1, rotate: 0 },\n exit: (direction) => ({\n opacity: 0,\n x: direction > 0 ? -100 : 100,\n scale: 0.8,\n position: "absolute",\n }),\n }}\n transition={{ duration: 0.25 }}\n initial={"enter"}\n animate={"center"}\n exit={"exit"}\n custom={direction}\n className={className}\n >\n {children}\n </motion.div>\n </AnimatePresence>\n );\n};\n\nexport const Framer = { Tabs, Content };\n',l='export function Circle() {\n return (\n <svg\n width="200"\n height="200"\n viewBox="0 0 340 340"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M340 170C340 263.888 263.888 340 170 340C76.1116 340 0 263.888 0 170C0 76.1116 76.1116 0 170 0C263.888 0 340 76.1116 340 170Z"\n fill="#FE65B7"\n />\n </svg>\n );\n }\n \n export function Square() {\n return (\n <svg\n width="200"\n height="200"\n viewB
1ox="0 0 412 412"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <rect\n x="211.48"\n y="-6"\n width="300"\n height="300"\n rx="15"\n transform="rotate(46.4634 211.48 -6)"\n fill="#446DF6"\n />\n </svg>\n );\n }\n \n export function Triangle() {\n return (\n <svg\n width="200"\n height="208"\n viewBox="0 0 349 364"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M328.852 1.79834C339.737 -2.05613 350.712 7.31786 348.608 18.6715L287.058 350.766C284.954 362.119 271.348 366.937 262.568 359.438L5.74062 140.087C-3.03979 132.588 -0.409476 118.396 10.4752 114.542L328.852 1.79834Z"\n fill="#AB87FF"\n />\n </svg>\n );\n } \n',CSSExample=()=>(0,s.jsx)(a.B,{previewHeightClassname:"h-100",files:{"/css.tsx":{active:!0,code:'import classNames from "classnames";\nimport {\n PointerEvent,\n FocusEvent,\n useEffect,\n useRef,\n useState,\n CSSProperties,\n} from "react";\n\ntype Tab = { label: string; id: string };\n\ntype Props = {\n selectedTabIndex: number;\n tabs: Tab[];\n setSelectedTab: (input: number) => void;\n};\n\nexport const CSSTabs = ({\n tabs,\n selectedTabIndex,\n setSelectedTab,\n}: Props): JSX.Element => {\n const [buttonRefs, setButtonRefs] = useState<Array<HTMLButtonElement | null>>(\n []\n );\n\n useEffect(() => {\n setButtonRefs((prev) => prev.slice(0, tabs.length));\n }, [tabs.length]);\n\n const [hoveredTabIndex, setHoveredTabIndex] = useState<number | null>(null);\n const [hoveredRect, setHoveredRect] = useState<DOMRect | null>(null);\n\n const navRef = useRef<HTMLDivElement>(null);\n const navRect = navRef.current?.getBoundingClientRect();\n\n const selectedRect = buttonRefs[selectedTabIndex]?.getBoundingClientRect();\n\n const [isInitialHoveredElement, setIsInitialHoveredElement] = useState(true);\n const isInitialRender = useRef(true);\n\n const onLeaveTabs = () => {\n setIsInitialHoveredElement(true);\n setHoveredTabIndex(null);\n };\n\n const onEnterTab = (\n e: PointerEvent<HTMLButtonElement> | FocusEvent<HTMLButtonElement>,\n i: number\n ) => {\n if (!e.target || !(e.target instanceof HTMLButtonElement)) return;\n\n setHoveredTabIndex((prev) => {\n if (prev != null && prev !== i) {\n setIsInitialHoveredElement(false);\n }\n\n return i;\n });\n setHoveredRect(e.target.getBoundingClientRect());\n };\n\n const onSelectTab = (i: number) => {\n setSelectedTab(i);\n };\n\n let hoverStyles: CSSProperties = { opacity: 0 };\n if (navRect && hoveredRect) {\n hoverStyles.transform = `translate3d(${hoveredRect.left - navRect.left}px,${\n hoveredRect.top - navRect.top\n }px,0px)`;\n hoverStyles.width = hoveredRect.width;\n hoverStyles.height = hoveredRect.height;\n hoverStyles.opacity = hoveredTabIndex != null ? 1 : 0;\n hoverStyles.transition = isInitialHoveredElement\n ? `opacity 150ms`\n : `transform 150ms 0ms, opacity 150ms 0ms, width 150ms`;\n }\n\n let selectStyles: CSSProperties = { opacity: 0 };\n if (navRect && selectedRect) {\n selectStyles.width = selectedRect.width * 0.8;\n selectStyles.transform = `translateX(calc(${\n selectedRect.left - navRect.left\n }px + 10%))`;\n selectStyles.opacity = 1;\n selectStyles.transition = isInitialRender.current\n ? `opacity 150ms 150ms`\n : `transform 150ms 0ms, opacity 150ms 150ms, width 150ms`;\n\n isInitialRender.current = false;\n }\n\n return (\n <nav\n ref={navRef}\n className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"\n onPointerLeave={onLeaveTabs}\n >\n {tabs.map((item, i) => {\n return (\n <button\n key={i}\n className={classNames(\n "text-md relative rounded-md flex items-center h-8 px-4 z-20 bg-transparent text-sm text-slate-500 cursor-pointer select-none transition-colors",\n {\n "text-slate-700":\n hoveredTabIndex === i || selectedTabIndex === i,\n }\n )}\n ref={(el) => (buttonRefs[i] = el)}\n onPointerEnter={(e) => onEnterTab(e, i)}\n onFocus={(e) => onEnterTab(e, i)}\n onClick={() => onSelectTab(i)}\n >\n {item.label}\n </button>\n );\n })}\n <div\n className="absolute z-10 top-0 left-0 rounded-md bg-gray-200 transition-[width]"\n style={hoverStyles}\n />\n <div\n className={"absolute z-10 bottom-0 left-0 h-0.5 bg-slate-500"}\n style={selectStyles}\n />\n </nav>\n );\n};\n\n'}
1,"/App.tsx":'import { useState } from "react";\nimport { CSSTabs } from "./css";\nimport { useTabs } from "./useTabs";\nimport { Circle, Square, Triangle } from "./shapes";\n\nexport default function App() {\n const [hookProps] = useState({\n tabs: [\n {\n label: "Circle",\n children: <Circle />,\n id: "Circle",\n },\n {\n label: "Triangle",\n children: <Triangle />,\n id: "Triangle",\n },\n {\n label: "Square",\n children: <Square />,\n id: "Square",\n },\n ],\n initialTabId: "Triangle",\n });\n const css = useTabs(hookProps);\n return (\n <div className="w-full flex flex-col space-y-24 items-center justify-center">\n <div className="max-w-6xl">\n <CSSTabs {...css.tabProps} />\n <div className="py-9 flex flex-col items-center">{css.selectedTab.children}</div>\n </div>\n </div>\n );\n}\n',"/shapes.tsx":'export function Circle() {\n return (\n <svg\n width="200"\n height="200"\n viewB
1ox="0 0 340 340"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M340 170C340 263.888 263.888 340 170 340C76.1116 340 0 263.888 0 170C0 76.1116 76.1116 0 170 0C263.888 0 340 76.1116 340 170Z"\n fill="#FE65B7"\n />\n </svg>\n );\n }\n \n export function Square() {\n return (\n <svg\n width="200"\n height="200"\n viewBox="0 0 412 412"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <rect\n x="211.48"\n y="-6"\n width="300"\n height="300"\n rx="15"\n transform="rotate(46.4634 211.48 -6)"\n fill="#446DF6"\n />\n </svg>\n );\n }\n \n export function Triangle() {\n return (\n <svg\n width="200"\n height="208"\n viewBox="0 0 349 364"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M328.852 1.79834C339.737 -2.05613 350.712 7.31786 348.608 18.6715L287.058 350.766C284.954 362.119 271.348 366.937 262.568 359.438L5.74062 140.087C-3.03979 132.588 -0.409476 118.396 10.4752 114.542L328.852 1.79834Z"\n fill="#AB87FF"\n />\n </svg>\n );\n }\n ',"/useTabs.tsx":'import { ReactNode, useState } from "react";\n\ntype Tab = { label: string; id: string; children: ReactNode };\n\nexport function useTabs({\n tabs,\n initialTabId,\n onChange,\n}: {\n tabs: Tab[];\n initialTabId: string;\n onChange?: (id: string) => void;\n}) {\n const [selectedTabIndex, setSelectedTab] = useState(() =>
1 {\n const indexOfInitialTab = tabs.findIndex((tab) => tab.id === initialTabId);\n return indexOfInitialTab === -1 ? 0 : indexOfInitialTab;\n });\n\n return {\n tabProps: {\n tabs,\n selectedTabIndex,\n onChange,\n setSelectedTab,\n },\n selectedTab: tabs[selectedTabIndex],\n };\n}\n'},customSetup:{dependencies:{classnames:"^2.3.1"}}}),TransitionGroupExample=()=>(0,s.jsx)(a.B,{previewHeightClassname:"h-100",files:{"/transition-group.tsx":{active:!0,code:'import classNames from "classnames";\nimport {\n useEffect,\n useRef,\n useState,\n PointerEvent,\n FocusEvent,\n CSSProperties,\n} from "react";\nimport { Transition } from "react-transition-group";\n\nimport { Tab } from "./useTabs";\n\ntype Props = {\n selectedTabIndex: number;\n tabs: Tab[];\n setSelectedTab: (input: number) => void;\n};\n\nconst duration = 300;\n\nconst transitionStyles = {\n entering: {\n opacity: 1,\n transition: `transform 0ms, opacity 150ms, width 0ms`,\n },\n entered: {\n opacity: 1,\n transition: `transform 150ms 0ms, opacity 150ms 0ms, width 150ms`,\n },\n exiting: {\n opacity: 0,\n transition: `transform 0ms, opacity 150ms, width 0ms`,\n },\n exited: { opacity: 0 },\n unmounted: {},\n};\n\nexport const TransitionGroupTabs = ({\n tabs,\n selectedTabIndex,\n setSelectedTab,\n}: Props): JSX.Element => {\n const [buttonRefs, setButtonRefs] = useState<Array<HTMLButtonElement | null>>(\n []\n );\n\n useEffect(() => {\n setButtonRefs((prev) => prev.slice(0, tabs.length));\n }, [tabs.length]);\n\n const navRef = useRef<HTMLDivElement>(null);\n const navRect = navRef.current?.getBoundingClientRect();\n\n const [hoveredTabIndex, setHoveredTabIndex] = useState<number | null>(null);\n const [hoveredRect, setHoveredRect] = useState<DOMRect | null>(null);\n\n const selectedRect = buttonRefs[selectedTabIndex]?.getBoundingClientRect();\n\n const onLeaveTabs = () => {\n setHoveredTabIndex(null);\n };\n\n const onEnterTab = (\n e: PointerEvent<HTMLButtonElement> | FocusEvent<HTMLButtonElement>,\n i: number\n ) => {\n if (!e.target || !(e.target instanceof HTMLButtonElement)) return;\n\n setHoveredTabIndex(i);\n setHoveredRect(e.target.getBoundingClientRect());\n };\n\n const onSelectTab = (i: number) => {\n setSelectedTab(i);\n };\n\n const hoverStyles: CSSProperties =\n navRect && hoveredRect\n ? {\n transform: `translate3d(${hoveredRect.left - navRect.left}px,${\n hoveredRect.top - navRect.top\n }px,0px)`,\n width: hoveredRect.width,\n height: hoveredRect.height,\n }\n : {};\n\n const selectStyles: CSSProperties =\n navRect && selectedRect\n ? {\n width: selectedRect.width * 0.8,\n transform: `translateX(calc(${\n selectedRect.left - navRect.left\n }px + 10%))`,\n }\n : {};\n\n return (\n <nav\n ref={navRef}\n className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"\n onPointerLeave={onLeaveTabs}\n >\n {tabs.map((item, i) => {\n return (\n <button\n key={i}\n className={classNames(\n "text-md relative rounded-md flex items-center h-8 px-4 z-20 bg-transparent text-sm text-slate-500 cursor-pointer select-none transition-colors",\n {\n "text-slate-700":\n hoveredTabIndex === i || selectedTabIndex === i,\n }\n )}\n ref={(el) => (buttonRefs[i] = el)}\n onPointerEnter={(e) => onEnterTab(e, i)}\n onFocus={(e) => onEnterTab(e, i)}\n onClick={() => onSelectTab(i)}\n >\n {item.label}\n </button>\n );\n })}\n\n <Transition in={hoveredTabIndex != null} timeout={duration}>\n {(state) => (\n <div\n className="absolute z-10 top-0 left-0 rounded-md bg-gray-200 transition-[width]"\n style={{\n ...hoverStyles,\n ...transitionStyles[state],\n }}\n />\n )}\n </Transition>\n <Transition in={selectedRect != null} timeout={duration}>\n {(state) => (\n <div\n className={"absolute z-10 bottom-0 left-0 h-0.5 bg-slate-500"}\n style={{\n ...selectStyles,\n ...transitionStyles[state],\n }}\n />\n )}\n </Transition>\n </nav>\n );\n};\n'}
1,"/App.tsx":'import { useState } from "react";\nimport { TransitionGroupTabs } from "./transition-group";\nimport { useTabs } from "./useTabs";\nimport { Circle, Square, Triangle } from "./shapes";\n\nexport default function App() {\n const [hookProps] = useState({\n tabs: [\n {\n label: "Circle",\n children: <Circle />,\n id: "Circle",\n },\n {\n label: "Triangle",\n children: <Triangle />,\n id: "Triangle",\n },\n {\n label: "Square",\n children: <Square />,\n id: "Square",\n },\n ],\n initialTabId: "Triangle",\n });\n const transitionGroup = useTabs(hookProps);\n return (\n <div className="w-full flex flex-col space-y-24 items-center justify-center">\n <div className="max-w-6xl">\n <TransitionGroupTabs {...transitionGroup.tabProps} />\n <div className="py-9 flex flex-col items-center">{transitionGroup.selectedTab.children}</div>\n </div>\n </div>\n );\n}\n',"/shapes.tsx":'export function Circle() {\n return (\n <svg\n width="200"\n height="200"\n viewB
1ox="0 0 340 340"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M340 170C340 263.888 263.888 340 170 340C76.1116 340 0 263.888 0 170C0 76.1116 76.1116 0 170 0C263.888 0 340 76.1116 340 170Z"\n fill="#FE65B7"\n />\n </svg>\n );\n }\n \n export function Square() {\n return (\n <svg\n width="200"\n height="200"\n viewBox="0 0 412 412"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <rect\n x="211.48"\n y="-6"\n width="300"\n height="300"\n rx="15"\n transform="rotate(46.4634 211.48 -6)"\n fill="#446DF6"\n />\n </svg>\n );\n }\n \n export function Triangle() {\n return (\n <svg\n width="200"\n height="208"\n viewBox="0 0 349 364"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M328.852 1.79834C339.737 -2.05613 350.712 7.31786 348.608 18.6715L287.058 350.766C284.954 362.119 271.348 366.937 262.568 359.438L5.74062 140.087C-3.03979 132.588 -0.409476 118.396 10.4752 114.542L328.852 1.79834Z"\n fill="#AB87FF"\n />\n </svg>\n );\n }\n ',"/useTabs.tsx":'import { ReactNode, useState } from "react";\n\nexport type Tab = { label: string; id: string; children: ReactNode };\n\nexport function useTabs({\n tabs,\n initialTabId,\n onChange,\n}: {\n tabs: Tab[];\n initialTabId: string;\n onChange?: (id: string) => void;\n}) {\n const [selectedTabIndex, setSelectedTab] = useState(() =>
1 {\n const indexOfInitialTab = tabs.findIndex((tab) => tab.id === initialTabId);\n return indexOfInitialTab === -1 ? 0 : indexOfInitialTab;\n });\n\n return {\n tabProps: {\n tabs,\n selectedTabIndex,\n onChange,\n setSelectedTab,\n },\n selectedTab: tabs[selectedTabIndex],\n };\n}\n'},customSetup:{dependencies:{classnames:"^2.3.1","react-transition-group":"^4.4.2"}}}),SpringExample=()=>(0,s.jsx)(a.B,{previewHeightClassname:"h-100",files:{"/spring.tsx":{code:'import classNames from "classnames";\nimport { useEffect, useRef, useState } from "react";\nimport { useTransition, animated, useSpring, easings } from "react-spring";\n\nimport { Tab } from "./useTabs";\n\ntype Props = {\n selectedTabIndex: number;\n tabs: Tab[];\n setSelectedTab: (input: [number, number]) => void;\n};\n\nexport const Tabs = ({\n tabs,\n selectedTabIndex,\n setSelectedTab,\n}: Props): JSX.Element => {\n const [buttonRefs, setButtonRefs] = useState<Array<HTMLButtonElement | null>>(\n []\n );\n\n useEffect(() => {\n setButtonRefs((prev) => prev.slice(0, tabs.length));\n }, [tabs.length]);\n\n const navRef = useRef<HTMLDivElement>(null);\n const navRect = navRef.current?.getBoundingClientRect();\n\n const selectedRect = buttonRefs[selectedTabIndex]?.getBoundingClientRect();\n\n const [hoveredTabIndex, setHoveredTabIndex] = useState<number | null>(null);\n const hoveredRect =\n buttonRefs[hoveredTabIndex ?? -1]?.getBoundingClientRect();\n\n const onLeaveTabs = () => {\n setHoveredTabIndex(null);\n };\n\n const onEnterTab = (i: number) => {\n setHoveredTabIndex(i);\n };\n\n const onSelectTab = (i: number) => {\n setSelectedTab([i, i > selectedTabIndex ? 1 : -1]);\n };\n\n const stylesChangingOnUpdate =\n hoveredRect && navRect\n ? {\n transform: `translate3d(${hoveredRect.left - navRect.left}px,${\n hoveredRect.top - navRect.top\n }px,0px)`,\n width: hoveredRect.width,\n height: hoveredRect.height,\n }\n : {};\n\n const bgTransition = useTransition(hoveredTabIndex != null, {\n from: () => ({\n ...stylesChangingOnUpdate,\n opacity: 0,\n }),\n enter: {\n ...stylesChangingOnUpdate,\n opacity: 1,\n },\n update: stylesChangingOnUpdate,\n leave: { opacity: 0 },\n config: {\n duration: 150,\n easing: easings.easeOutCubic,\n },\n });\n\n const underlineStyles = useSpring({\n to:\n selectedRect && navRect\n ? {\n width: selectedRect.width * 0.8,\n transform: `translateX(calc(${\n selectedRect.left - navRect.left\n }px + 10%))`,\n opacity: 1,\n }\n : { opacity: 0 },\n config: {\n duration: 150,\n easing: easings.easeOutCubic,\n },\n });\n\n return (\n <nav\n ref={navRef}\n className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"\n onPointerLeave={onLeaveTabs}\n >\n {tabs.map((item, i) => {\n return (\n <button\n key={i}\n className={classNames(\n "text-md relative rounded-md flex items-center h-8 px-4 z-20 bg-transparent text-sm text-slate-500 cursor-pointer select-none transition-colors",\n {\n "text-slate-700":\n hoveredTabIndex === i || selectedTabIndex === i,\n }\n )}\n ref={(el) => (buttonRefs[i] = el)}\n onPointerEnter={() => onEnterTab(i)}\n onFocus={() => onEnterTab(i)}\n onClick={() => onSelectTab(i)}\n >\n {item.label}\n </button>\n );\n })}\n {bgTransition((styles) => (\n <animated.div\n className="absolute z-10 rounded-md top-0 left-0 bg-gray-200"\n style={styles}\n />\n ))}\n\n <animated.div\n className="absolute bottom-0 left-0 z-10 h-0.5 bg-slate-500"\n style={underlineStyles}\n />\n </nav>\n );\n};\n\nconst Content = ({\n selectedTabIndex,\n direction,\n tabs,\n className,\n}: {\n selectedTabIndex: number;\n direction: number;\n tabs: Tab[];\n\n className?: string;\n}): JSX.Element => {\n const transitions = useTransition(selectedTabIndex, {\n exitBeforeEnter: false,\n keys: null,\n from: {\n opacity: 0,\n transform: `translate3d(${\n direction > 0 ? "100" : "-100"\n }px,0,0) scale(0.8)`,\n },\n enter: { opacity: 1, transform: "translate3d(0px,0,0) scale(1)" },\n leave: {\n opacity: 0,\n transform: `translate3d(${\n direction > 0 ? "-100" : "100"\n }px,0,0) scale(0.8)`,\n position: "absolute",\n }
1,\n config: {\n duration: 250,\n easing: easings.easeOutCubic,\n },\n });\n\n return transitions((styles, item) => (\n <animated.div key={selectedTabIndex} style={styles} className={className}>\n {tabs[item].children}\n </animated.div>\n ));\n};\n\nexport const Spring = { Tabs, Content };\n',active:!0},"/App.tsx":'import { useState } from "react";\nimport { Spring } from "./spring";\nimport { useTabs } from "./useTabs";\nimport { Circle, Square, Triangle } from "./shapes";\n\nexport default function App() {\n const [hookProps] = useState({\n tabs: [\n {\n label: "Circle",\n children: <Circle />,\n id: "Circle",\n },\n {\n label: "Triangle",\n children: <Triangle />,\n id: "Triangle",\n },\n {\n label: "Square",\n children: <Square />,\n id: "Square",\n },\n ],\n initialTabId: "Triangle",\n });\n const spring = useTabs(hookProps);\n return (\n <div className="w-full flex flex-col space-y-24 items-center justify-center">\n <div className="max-w-6xl">\n <Spring.Tabs {...spring.tabProps} />\n <Spring.Content {...spring.contentProps} className="py-9 flex flex-col items-center" />\n </div>\n </div>\n );\n}\n\n',"/shapes.tsx":'export function Circle() {\n return (\n <svg\n width="200"\n height="200"\n viewB
1ox="0 0 340 340"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M340 170C340 263.888 263.888 340 170 340C76.1116 340 0 263.888 0 170C0 76.1116 76.1116 0 170 0C263.888 0 340 76.1116 340 170Z"\n fill="#FE65B7"\n />\n </svg>\n );\n }\n \n export function Square() {\n return (\n <svg\n width="200"\n height="200"\n viewBox="0 0 412 412"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <rect\n x="211.48"\n y="-6"\n width="300"\n height="300"\n rx="15"\n transform="rotate(46.4634 211.48 -6)"\n fill="#446DF6"\n />\n </svg>\n );\n }\n \n export function Triangle() {\n return (\n <svg\n width="200"\n height="208"\n viewBox="0 0 349 364"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M328.852 1.79834C339.737 -2.05613 350.712 7.31786 348.608 18.6715L287.058 350.766C284.954 362.119 271.348 366.937 262.568 359.438L5.74062 140.087C-3.03979 132.588 -0.409476 118.396 10.4752 114.542L328.852 1.79834Z"\n fill="#AB87FF"\n />\n </svg>\n );\n }\n ',"/useTabs.tsx":'import { ReactNode, useState } from "react";\n\nexport type Tab = { label: string; id: string; children: ReactNode };\n\nexport function useTabs({\n tabs,\n initialTabId,\n onChange,\n}: {\n tabs: Tab[];\n initialTabId: string;\n onChange?: (id: string) => void;\n}) {\n const [[selectedTabIndex, direction], setSelectedTab] = useState(() =>
1 {\n const indexOfInitialTab = tabs.findIndex((tab) => tab.id === initialTabId);\n return [indexOfInitialTab === -1 ? 0 : indexOfInitialTab, 0];\n });\n\n return {\n tabProps: {\n tabs,\n selectedTabIndex,\n onChange,\n setSelectedTab,\n },\n selectedTab: tabs[selectedTabIndex],\n contentProps: {\n direction,\n selectedTabIndex,\n tabs,\n },\n };\n}\n'},customSetup:{dependencies:{classnames:"^2.3.1","react-spring":"^9.5.0"}}}),FramerExample=n=>(0,s.jsx)(a.B,{previewHeightClassname:"h-100",files:{"/framer.tsx":{code:o,active:!0},"/App.tsx":r,"/shapes.tsx":l,"/useTabs.tsx":i},customSetup:{dependencies:{classnames:"^2.3.1","framer-motion":"^6.4.3"}}}),FramerLayoutExample=()=>(0,s.jsx)(a.B,{previewHeightClassname:"h-100",files:{"/framer-layout.tsx":{code:'import classNames from "classnames";\nimport { AnimatePresence, LayoutGroup, motion } from "framer-motion";\nimport { ReactNode, useState } from "react";\n\nimport { Tab } from "./useTabs";\n\nconst transition = {\n type: "tween",\n ease: "easeOut",\n duration: 0.15,\n};\n\ntype Props = {\n selectedTabIndex: number;\n tabs: Tab[];\n setSelectedTab: (input: [number, number]) => void;\n};\n\nexport const Tabs = ({\n tabs,\n selectedTabIndex,\n setSelectedTab,\n}: Props): JSX.Element => {\n const [hoveredTab, setHoveredTab] = useState<number | null>(null);\n return (\n <motion.nav\n className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"\n onHoverEnd={() => setHoveredTab(null)}\n >\n <LayoutGroup id="tabs">\n {tabs.map((item, i) => {\n return (\n <motion.button\n key={i}\n className={classNames(\n "text-md relative rounded-md flex items-center h-8 px-4 text-sm text-slate-500 cursor-pointer select-none transition-colors",\n {\n "text-slate-700": hoveredTab === i || selectedTabIndex === i,\n }\n )}\n onHoverStart={() => setHoveredTab(i)}\n onFocus={() => setHoveredTab(i)}\n onClick={() => {\n setSelectedTab([i, i > selectedTabIndex ? 1 : -1]);\n }}\n >\n <span className="z-20">{item.label}</span>\n {i === selectedTabIndex ? (\n <motion.div\n transition={transition}\n layoutId="underline"\n className={\n "absolute z-10 h-0.5 left-2 right-2 -bottom-2 bg-slate-500"\n }\n />\n ) : null}\n <AnimatePresence>\n {i === hoveredTab ? (\n <motion.div\n className="absolute bottom-0 left-0 right-0 top-0 z-10 rounded-md bg-gray-200"\n initial={{\n opacity: 0,\n }}\n animate={{\n opacity: 1,\n }}\n exit={{\n opacity: 0,\n }}\n transition={transition}\n layoutId="hover"\n />\n ) : null}\n </AnimatePresence>\n </motion.button>\n );\n })}\n </LayoutGroup>\n </motion.nav>\n );\n};\n\nconst Content = ({\n children,\n className,\n selectedTabIndex,\n direction,\n}: {\n direction: number;\n selectedTabIndex: number;\n children: ReactNode;\n className?: string;\n}): JSX.Element => {\n return (\n <AnimatePresence exitBeforeEnter={false} custom={direction}>\n <motion.div\n key={selectedTabIndex}\n variants={{\n enter: (direction) => ({\n opacity: 0,\n x: direction > 0 ? 100 : -100,\n scale: 0.8,\n }),\n center: { opacity: 1, x: 0, scale: 1, rotate: 0 },\n exit: (direction) => ({\n opacity: 0,\n x: direction > 0 ? -100 : 100,\n scale: 0.8,\n position: "absolute",\n }),\n }}\n transition={{ duration: 0.25 }}\n initial={"enter"}\n animate={"center"}\n exit={"exit"}\n custom={direction}\n className={className}\n >\n {children}\n </motion.div>\n </AnimatePresence>\n );\n};\n\nexport const FramerLayout = { Tabs, Content };\n',active:!0}
1,"/App.tsx":'import { useState } from "react";\nimport { FramerLayout } from "./framer-layout";\nimport { useTabs } from "./useTabs";\nimport { Circle, Square, Triangle } from "./shapes";\n\nexport default function App() {\n const [hookProps] = useState({\n tabs: [\n {\n label: "Circle",\n children: <Circle />,\n id: "Circle",\n },\n {\n label: "Triangle",\n children: <Triangle />,\n id: "Triangle",\n },\n {\n label: "Square",\n children: <Square />,\n id: "Square",\n },\n ],\n initialTabId: "Triangle",\n });\n const framer = useTabs(hookProps);\n return (\n <div className="w-full flex flex-col space-y-24 items-center justify-center">\n <div className="max-w-6xl">\n <FramerLayout.Tabs {...framer.tabProps} />\n <FramerLayout.Content\n {...framer.contentProps}\n className="text-center rounded-3xl py-9 flex flex-col items-center"\n >\n {framer.selectedTab.children}\n </FramerLayout.Content>\n </div>\n </div>\n );\n}\n',"/shapes.tsx":'export function Circle() {\n return (\n <svg\n width="200"\n height="200"\n viewB
1ox="0 0 340 340"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M340 170C340 263.888 263.888 340 170 340C76.1116 340 0 263.888 0 170C0 76.1116 76.1116 0 170 0C263.888 0 340 76.1116 340 170Z"\n fill="#FE65B7"\n />\n </svg>\n );\n }\n \n export function Square() {\n return (\n <svg\n width="200"\n height="200"\n viewBox="0 0 412 412"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <rect\n x="211.48"\n y="-6"\n width="300"\n height="300"\n rx="15"\n transform="rotate(46.4634 211.48 -6)"\n fill="#446DF6"\n />\n </svg>\n );\n }\n \n export function Triangle() {\n return (\n <svg\n width="200"\n height="208"\n viewBox="0 0 349 364"\n fill="none"\n xmlns="http://www.w3.org/2000/svg"\n >\n <path\n d="M328.852 1.79834C339.737 -2.05613 350.712 7.31786 348.608 18.6715L287.058 350.766C284.954 362.119 271.348 366.937 262.568 359.438L5.74062 140.087C-3.03979 132.588 -0.409476 118.396 10.4752 114.542L328.852 1.79834Z"\n fill="#AB87FF"\n />\n </svg>\n );\n }\n ',"/useTabs.tsx":'import { ReactNode, useState } from "react";\n\nexport type Tab = { label: string; id: string; children: ReactNode };\n\nexport function useTabs({\n tabs,\n initialTabId,\n onChange,\n}: {\n tabs: Tab[];\n initialTabId: string;\n onChange?: (id: string) => void;\n}) {\n const [[selectedTabIndex, direction], setSelectedTab] = useState(() =>
1 {\n const indexOfInitialTab = tabs.findIndex((tab) => tab.id === initialTabId);\n return [indexOfInitialTab === -1 ? 0 : indexOfInitialTab, 0];\n });\n\n return {\n tabProps: {\n tabs,\n selectedTabIndex,\n onChange,\n setSelectedTab,\n },\n selectedTab: tabs[selectedTabIndex],\n contentProps: {\n direction,\n selectedTabIndex,\n },\n };\n}\n'},customSetup:{dependencies:{classnames:"^2.3.1","framer-motion":"^6.4.3"}}}),TabsDemo=n=>(0,s.jsx)(a.B,{...n,hideEditor:!0,previewHeightClassname:"h-100",files:{"/framer.tsx":{code:o,active:!0},"/App.tsx":r,"/shapes.tsx":l,"/useTabs.tsx":i},customSetup:{dependencies:{classnames:"^2.3.1","framer-motion":"^6.4.3"}}})}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.