Todos os componentes

Single Layout Accordion

Single Layout Accordion é um componente de layout para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

UI Layouts MIT layout

O que é

Single Layout Accordion é um componente de layout para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Não precisa de pacotes além do que o projeto já tem

Licença

Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Single Layout Accordion

./registry/components/accordion/singlelayout.tsx
'use client';
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionWrapper,
} from '@/components/ui/accordion';
import { Plus } from 'lucide-react';
import React from 'react';

function SingleLayout() {
  return (
    <>
      <Accordion defaultValue={['item-1', 'item-2']}>
        <AccordionItem value='item-1' className='w-full dark:bg-black bg-white'>
          <AccordionHeader customIcon className='dark:text-neutral-300 dark:hover:text-black'>
            What is a UI component?{' '}
            <Plus className='group-data-active:rotate-45 transition-transform' />
          </AccordionHeader>
          <AccordionPanel>
            A UI (User Interface) component is a modular, reusable element that serves a specific
            function within a graphical user interface. Examples include buttons, input fields,
            dropdown menus, sliders, and checkboxes.
          </AccordionPanel>
        </AccordionItem>
        <AccordionItem value='item-2' className='w-full dark:bg-black bg-white'>
          <AccordionHeader customIcon className='dark:text-neutral-300 dark:hover:text-black'>
            Why are UI components important?{' '}
            <Plus className='group-data-active:rotate-45 transition-transform' />
          </AccordionHeader>
          <AccordionPanel>
            UI components promote consistency, efficiency, and scalability in software development.
            They allow developers to reuse code, maintain a consistent look and feel across an
            application, and easily make updates or modifications without affecting the entire
            system.
          </AccordionPanel>
        </AccordionItem>
        <AccordionItem value='item-3' className='w-full dark:bg-black bg-white'>
          <AccordionHeader customIcon className='dark:text-neutral-300 dark:hover:text-black'>
            Key characteristics of UI components?{' '}
            <Plus className='group-data-active:rotate-45 transition-transform' />
          </AccordionHeader>
          <AccordionPanel>
            Well-designed UI components should be modular, customizable, and accessible. They should
            have clear and intuitive functionality, be easily styled to match the overall design
            language of the application.
          </AccordionPanel>
        </AccordionItem>
      </Accordion>
    </>
  );
}

export default SingleLayout;
./components/ui/accordion.tsx
'use client';

import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import React, { type ReactNode, useCallback } from 'react';

/**
 * Interface for AccordionContext values
 */
interface AccordionContextType {
  /**
   * Whether the accordion item is active
   */
  isActive?: boolean;
  /**
   * The value of the accordion item
   */
  value?: string;
  /**
   * Function to change the active index
   */
  onChangeIndex?: (value: string) => void;
}

/**
 * Context for accordion components
 */
const AccordionContext = React.createContext<AccordionContextType>({});
/**
 * Hook to use the accordion context
 */
const useAccordion = () => React.useContext(AccordionContext);

/**
 * Container component for accordion items
 */
export function AccordionContainer({
  children,
  className,
}: {
  children: ReactNode;
  className?: string;
}) {
  return <div className={cn('grid grid-cols-2 gap-1', className)}>{children}</div>;
}

/**
 * Wrapper component for accordion items
 */
export function AccordionWrapper({ children }: { children: ReactNode }) {
  return <div>{children}</div>;
}

/**
 * Interface for Accordion props
 */
interface AccordionProps {
  /**
   * Children components
   */
  children: ReactNode;
  /**
   * Whether multiple items can be active at the same time
   */
  multiple?: boolean;
  /**
   * Default active index
   */
  defaultValue?: string | string[];
}

/**
 * Accordion component
 */
export function Accordion({ children, multiple, defaultValue }: AccordionProps) {
  /**
   * State for active index
   */
  const [activeIndex, setActiveIndex] = React.useState<string | string[] | null>(
    multiple ? (Array.isArray(defaultValue) ? defaultValue : []) : defaultValue || null
  );

  /**
   * Function to change the active index
   */
  const onChangeIndex = useCallback(
    (value: string) => {
      setActiveIndex((currentActiveIndex) => {
        if (!multiple) {
          return value === currentActiveIndex ? null : value;
        }

        if (Array.isArray(currentActiveIndex)) {
          if (currentActiveIndex.includes(value)) {
            return currentActiveIndex.filter((i) => i !== value);
          }
          return [...currentActiveIndex, value];
        }

        return [value];
      });
    },
    [multiple]
  );

  return React.Children.map(children, (child) => {
    if (!React.isValidElement(child)) return null;

    const childProps = child.props as { value: string };
    const value = childProps.value;
    const isActive = multiple
      ? Array.isArray(activeIndex) && activeIndex.includes(value)
      : activeIndex === value;

    return (
      <AccordionContext.Provider value={{ isActive, value, onChangeIndex }}>
        {child}
      </AccordionContext.Provider>
    );
  });
}

/**
 * Interface for AccordionItem props
 */
interface AccordionItemProps {
  /**
   * Children components
   */
  children: ReactNode;
  /**
   * Value of the accordion item
   */
  value: string;
  className?: string;
}

/**
 * Accordion item component
 */
export function AccordionItem({ children, value, className }: AccordionItemProps) {
  const { isActive } = useAccordion();

  return (
    <div
      data-active={isActive || undefined}
      className={cn(
        'rounded-lg overflow-hidden mb-2 group border border-neutral-200 dark:border-neutral-800',
        className
      )}
    >
      {children}
    </div>
  );
}

/**
 * Interface for AccordionHeader props
 */
interface AccordionHeaderProps {
  /**
   * Children components
   */
  children: ReactNode;
  /**
   * Icon component
   */
  customIcon?: boolean;
  className?: string;
}

/**
 * Accordion header component
 */
export function AccordionHeader({ children, customIcon, className }: AccordionHeaderProps) {
  const { isActive, value, onChangeIndex } = useAccordion();

  const handleClick = useCallback(() => {
    if (value && onChangeIndex) {
      onChangeIndex(value);
    }
  }, [onChangeIndex, value]);

  return (
    <motion.button
      type='button'
      data-active={isActive || undefined}
      aria-expanded={isActive}
      className={cn(
        'p-4 cursor-pointer w-full transition-all font-semibold text-neutral-500 dark:data-active:text-neutral-200 data-active:text-neutral-800 dark:data-active:bg-neutral-800 data-active:bg-neutral-200 hover:bg-neutral-100 hover:text-black flex justify-between gap-2 items-center text-left',
        className
      )}
      onClick={handleClick}
    >
      {children}
      {!customIcon && (
        <ChevronDown
          className={cn(
            'transition-transform shrink-0 text-neutral-500 dark:text-neutral-400',
            isActive ? 'rotate-180' : 'rotate-0'
          )}
          aria-hidden='true'
        />
      )}
    </motion.button>
  );
}
/**
 * Interface for AccordionPanel props
 */
interface AccordionPanelProps {
  /**
   * Children components
   */
  children: ReactNode;
  /**
   * className
   */
  className?: string;
  /**
   * article className
   */
  articleClassName?: string;
}

/**
 * Accordion panel component
 */
export function AccordionPanel({ children, className, articleClassName }: AccordionPanelProps) {
  const { isActive, value } = useAccordion();

  return (
    <AnimatePresence initial={true}>
      {isActive && (
        <motion.div
          data-active={isActive || undefined}
          role='region'
          id={`accordion-panel-${value}`}
          aria-labelledby={`accordion-header-${value}`}
          initial={{ height: 0, overflow: 'hidden' }}
          animate={{ height: 'auto', overflow: 'hidden' }}
          exit={{ height: 0 }}
          transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
          className={cn(
            'bg-neutral-100 dark:bg-neutral-900 px-2 data-active:bg-neutral-200 dark:data-active:bg-neutral-800 text-black dark:text-white',
            className
          )}
        >
          <motion.div
            initial={{ clipPath: 'polygon(0 0, 100% 0, 100% 0, 0 0)' }}
            animate={{ clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0% 100%)' }}
            exit={{
              clipPath: 'polygon(0 0, 100% 0, 100% 0, 0 0)',
            }}
            transition={{
              type: 'spring',
              duration: 0.4,
              bounce: 0,
            }}
            className={cn('px-3 bg-transparent pb-4 space-y-2', articleClassName)}
          >
            {children}
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

Componentes parecidos