All components

Creative Tab

Creative Tab is a React and Tailwind CSS navigation component from UI Layouts, licensed MIT. Copy it and paste it into your project.

UI Layouts MIT navigation

What it is

Creative Tab is a React and Tailwind CSS navigation component from UI Layouts, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i motion

Licence

This component comes from UI Layouts and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Creative Tab

./components/ui/tab.tsx
'use client';

import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'motion/react';
import React, {
  createContext,
  isValidElement,
  type ReactNode,
  useCallback,
  useContext,
  useMemo,
  useState,
} from 'react';

// Improved TypeScript interfaces with more specific types
interface TabContextType {
  activeTab: string;
  setActiveTab: (value: string) => void;
  wobbly: boolean;
  hover: boolean;
  defaultValue: string;
  prevIndex: number;
  setPrevIndex: (value: number) => void;
  tabsOrder: string[];
}

const TabContext = createContext<TabContextType | undefined>(undefined);

// Custom hook with memoization
export const useTabs = () => {
  const context = useContext(TabContext);
  if (!context) {
    throw new Error('useTabs must be used within a TabsProvider');
  }
  return context;
};

// Props interfaces with more specific types
interface TabsProviderProps {
  children: ReactNode;
  defaultValue: string;
  wobbly?: boolean;
  hover?: boolean;
}

interface TabsBtnProps {
  children: ReactNode;
  className?: string;
  value: string;
}

interface TabsContentProps {
  children: ReactNode;
  className?: string;
  value: string;
  yValue?: boolean;
}

export const TabsProvider: React.FC<TabsProviderProps> = React.memo(
  ({ children, defaultValue, wobbly = true, hover = false }) => {
    // Use useCallback to memoize state setters
    const [activeTab, setActiveTab] = useState(defaultValue);
    const [prevIndex, setPrevIndex] = useState(0);

    // Memoize tabs order to prevent unnecessary recalculations
    const tabsOrder = useMemo(() => {
      return React.Children.toArray(children)
        .filter((child) => isValidElement(child) && child.type === TabsContent)
        .map((child) => (child as React.ReactElement<any>).props.value);
    }, [children]);

    // Memoize context value to prevent unnecessary re-renders
    const contextValue = useMemo(
      () => ({
        activeTab,
        setActiveTab,
        wobbly,
        hover,
        defaultValue,
        setPrevIndex,
        prevIndex,
        tabsOrder,
      }),
      [activeTab, setActiveTab, wobbly, hover, defaultValue, prevIndex, tabsOrder]
    );

    return <TabContext.Provider value={contextValue}>{children}</TabContext.Provider>;
  }
);

// Memoized TabsBtn component
export const TabsBtn: React.FC<TabsBtnProps> = React.memo(({ children, className, value }) => {
  const { activeTab, setPrevIndex, setActiveTab, defaultValue, hover, wobbly, tabsOrder } =
    useTabs();

  // Use useCallback to memoize the click handler
  const handleClick = useCallback(() => {
    setPrevIndex(tabsOrder.indexOf(activeTab));
    setActiveTab(value);
  }, [setPrevIndex, tabsOrder, activeTab, setActiveTab, value]);

  return (
    <motion.div
      className={cn(`cursor-pointer 2xl:p-2 p-2 2xl:px-4 px-2 rounded-md relative`, className)}
      onFocus={() => hover && handleClick()}
      onMouseEnter={() => hover && handleClick()}
      onClick={handleClick}
    >
      {children}

      <AnimatePresence mode='wait'>
        {activeTab === value && (
          <>
            <motion.div
              transition={{
                layout: {
                  duration: 0.2,
                  ease: 'easeInOut',
                  delay: 0.2,
                },
              }}
              layoutId={defaultValue}
              className='absolute w-full h-full left-0 top-0 dark:bg-primary-base bg-white rounded-md z-1'
            />

            {wobbly && (
              <>
                <motion.div
                  transition={{
                    layout: {
                      duration: 0.4,
                      ease: 'easeInOut',
                      delay: 0.04,
                    },
                  }}
                  layoutId={defaultValue}
                  className='absolute w-full h-full left-0 top-0 dark:bg-primary-base bg-white rounded-md z-1 tab-shadow'
                />
                <motion.div
                  transition={{
                    layout: {
                      duration: 0.4,
                      ease: 'easeOut',
                      delay: 0.2,
                    },
                  }}
                  layoutId={`${defaultValue}b`}
                  className='absolute w-full h-full left-0 top-0 dark:bg-primary-base bg-white rounded-md z-1 tab-shadow'
                />
              </>
            )}
          </>
        )}
      </AnimatePresence>
    </motion.div>
  );
});

// Memoized TabsContent component
export const TabsContent: React.FC<TabsContentProps> = React.memo(
  ({ children, className, value, yValue }) => {
    const { activeTab, tabsOrder, prevIndex } = useTabs();

    // Memoize direction calculation
    const isForward = useMemo(
      () => tabsOrder.indexOf(activeTab) > prevIndex,
      [tabsOrder, activeTab, prevIndex]
    );

    return (
      <AnimatePresence mode='popLayout'>
        {activeTab === value && (
          <motion.div
            initial={{ opacity: 0, y: yValue ? (isForward ? 10 : -10) : 0 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: yValue ? (isForward ? -50 : 50) : 0 }}
            transition={{
              duration: 0.3,
              ease: 'easeInOut',
              delay: 0.5,
            }}
            className={cn('p-2 px-4 rounded-md relative', className)}
          >
            {children}
          </motion.div>
        )}
      </AnimatePresence>
    );
  }
);

// Add display names for better debugging
TabsProvider.displayName = 'TabsProvider';
TabsBtn.displayName = 'TabsBtn';
TabsContent.displayName = 'TabsContent';
./registry/components/tabs/home-tab.tsx
// thanks to maximeheckel

import preview from '@/assets/preview/Preview';
import { TabsBtn, TabsContent, TabsProvider } from '@/components/ui/tab';
import Image from 'next/image';
import React, { useState } from 'react';

function HomeTab() {
  return (
    <>
      <TabsProvider defaultValue={'accordion'} wobbly={true}>
        <div className='flex justify-center pt-4'>
          <div className='flex items-center capitalize w-fit dark:bg-[#1a1c20] bg-neutral-300 p-1 dark:text-white text-black rounded-md border'>
            <TabsBtn value='accordion'>
              <span className='relative z-2 '>Accordion</span>
            </TabsBtn>
            <TabsBtn value='globe'>
              <span className='relative z-2 '>Globe</span>
            </TabsBtn>
            <TabsBtn value='mousetrail'>
              <span className='relative z-2 '>Mouse Trail</span>
            </TabsBtn>
          </div>
        </div>

        <TabsContent value='accordion' yValue={false}>
          <div className='dark:bg-black bg-white  mx-auto max-w-[450px] lg:h-[300px] md:h-[240px] h-[190px]  dark:text-white text-black p-4 border  shadow-md rounded-md space-y-2'>
            <Image
              className='rounded-xl h-full object-contain'
              src={preview.galleryNew}
              width={600}
              height={680}
              alt={'gallery'}
            />
          </div>
        </TabsContent>
        <TabsContent value='globe' yValue={false}>
          <div className='dark:bg-black bg-white  mx-auto max-w-[450px] lg:h-[300px] md:h-[240px] h-[190px] dark:text-white text-black p-4 border shadow-md rounded-md space-y-2'>
            <Image
              className='rounded-xl h-full object-contain'
              src={preview.globe}
              width={600}
              height={680}
              alt={'globe'}
            />
          </div>
        </TabsContent>
        <TabsContent value='mousetrail' yValue={false}>
          <div className='dark:bg-black bg-white  mx-auto max-w-[450px] lg:h-[300px] md:h-[240px] h-[190px] dark:text-white text-black p-4 border shadow-md rounded-md space-y-2'>
            <Image
              className='rounded-xl h-full object-contain'
              src={preview.mousetrail}
              width={600}
              height={680}
              alt={'mousetrail'}
            />
          </div>
        </TabsContent>
      </TabsProvider>
    </>
  );
}

export default HomeTab;

Similar components