Todos os componentes

Grid Layout Accordion

Grid 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 é

Grid 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.

Grid Layout Accordion

./registry/components/accordion/gridlayout.tsx
'use client';

import {
  Accordion,
  AccordionContainer,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  AccordionWrapper,
} from '@/components/ui/accordion';
import React from 'react';

function SingleLayout() {
  return (
    <AccordionContainer className='md:grid-cols-2 grid-cols-1'>
      <AccordionWrapper>
        <Accordion defaultValue={'item-2'}>
          <AccordionItem value='item-1' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              What is a UI component?
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              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.
            </AccordionPanel>
          </AccordionItem>
          <AccordionItem value='item-2' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              Why are components important?
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              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.
            </AccordionPanel>
          </AccordionItem>
          <AccordionItem value='item-3' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              UI Component Traits
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              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.
            </AccordionPanel>
          </AccordionItem>
        </Accordion>
      </AccordionWrapper>
      <AccordionWrapper>
        <Accordion defaultValue={'item-4'}>
          <AccordionItem value='item-4' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              Does Component Improve UX?
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              UI components can improve UX by providing familiar, consistent interactions that make
              it easy for users to navigate and interact with an application byy using recognizable
              patterns.
            </AccordionPanel>
          </AccordionItem>
          <AccordionItem value='item-5' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              component design challenges?
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              Some common challenges include maintaining consistency across different devices and
              screen sizes, ensuring compatibility with various browsers and assistive technologies
              with ease of use.
            </AccordionPanel>
          </AccordionItem>
          <AccordionItem value='item-6' className='dark:bg-black bg-white'>
            <AccordionHeader className='2xl:text-base text-sm dark:text-neutral-300 dark:hover:text-black'>
              Ensure Responsiveness
            </AccordionHeader>
            <AccordionPanel className='2xl:text-base text-sm'>
              Developers can ensure the responsiveness of UI components by using techniques such as
              fluid layouts, flexible grids, and media queries to adapt the components to different
              screen sizes.
            </AccordionPanel>
          </AccordionItem>
        </Accordion>
      </AccordionWrapper>
    </AccordionContainer>
  );
}

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