Collapsible

100% RSC0kb

Example

This section provides a brief overview of the Collapsible Component, illustrating how it can be utilized to create expandable and collapsible sections within your application. Ideal for organizing content or hiding detailed information until requested by the user.

Requirements

pnpm add react-icons class-variance-authority
bun add react-icons class-variance-authority
yarn add react-icons class-variance-authority
npm install react-icons class-variance-authority

Code

import cn from '@/utils/cn'
import { applyPropsToChildrenOfType } from '@/utils/manipulateReactComponents'
import React, { useId } from 'react'
import { HiChevronRight } from 'react-icons/hi2'
import { type VariantProps, cva } from 'class-variance-authority'

const Collapsible: React.FC<
  VariantProps<typeof cvaCollapsibleContent> & {
    children: React.ReactNode
    className?: string
    openByDefault?: boolean
    duration?: number
    accordionId?: string
  }
> = ({ children, className, openByDefault = false, duration = 300, animation, accordionId }) => {
  const id = useId()
  children = applyPropsToChildrenOfType(children, { id, openByDefault, animation, accordionId }, [CollapsibleContent, CollapsibleTrigger])
  return (
    <div className={cn('relative', className)} style={{ '--animation-duration': `${duration}ms` } as React.CSSProperties}>
      {children}
    </div>
  )
}
Collapsible.displayName = 'Collapsible'

const CollapsibleTrigger: React.FC<{
  id?: string
  children: React.ReactNode
  openByDefault?: boolean
  className?: string
  classNames?: { icon?: string }
  accordionId?: string
}> = ({ id, classNames = {}, children, className, openByDefault, accordionId }) => (
  <>
    <input type={accordionId ? 'radio' : 'checkbox'} name={accordionId || undefined} id={id} className="peer hidden" defaultChecked={openByDefault} />
    <label htmlFor={id} className={cn('collapsible group grid cursor-pointer select-none grid-flow-col grid-cols-[minmax(0,_1fr)_auto] items-center', className)}>
      {children}
      <span
        className={cn(
          'h-8 w-8 justify-self-end p-1 text-white outline-none transition-transform duration-[var(--animation-duration)] peer-checked:group-[.collapsible]:rotate-90',
          classNames?.icon
        )}>
        <HiChevronRight className="h-full w-full" />
      </span>
    </label>
  </>
)
CollapsibleTrigger.displayName = 'CollapsibleTrigger'

const cvaCollapsibleContent = cva(
  [
    'group',
    'grid',
    'origin-top',
    'transform-gpu',
    'overflow-hidden',
    'transition-all',
    'duration-[--animation-duration]',
    'ease-in-out',
    'peer-checked:grid-rows-[1fr]',
    'grid-rows-[0fr]',
    'peer-[:not(:checked)]:!py-0'
  ],
  {
    variants: {
      animation: {
        fade: '[&>*]:invisible opacity-0 [&>*]:opacity-0 [&>*]:transition-all peer-checked:opacity-100 [&>*]:duration-300 peer-checked:[&>*]:visible peer-checked:[&>*]:opacity-100',
        'slide-down':
          'origin-top -translate-y-2 peer-checked:translate-y-0 [&>*]:invisible opacity-0 peer-checked:opacity-100 [&>*]:opacity-0 [&>*]:transition-all [&>*]:ease-in-out peer-checked:[&>*]:visible peer-checked:[&>*]:opacity-100',
        'scale-fade':
          'origin-top [&>*]:invisible scale-95 peer-checked:scale-100 opacity-0 peer-checked:opacity-100 [&>*]:opacity-0 [&>*]:transition-all [&>*]:duration-[--animation-duration] [&>*]:ease-in-out peer-checked:[&>*]:visible peer-checked:[&>*]:opacity-100',
        none: '[&>*]:visible'
      }
    }
  }
)

const CollapsibleContent: React.FC<
  VariantProps<typeof cvaCollapsibleContent> & {
    id?: string
    className?: string
    innerClassName?: string
    children: React.ReactNode
    accordionId?: string
  }
> = ({ children, className, innerClassName, animation = 'scale-fade' }) => (
  <div
    className={cn(
      cvaCollapsibleContent({
        animation
      }),
      className
    )}>
    <div className={cn('min-h-0', innerClassName)}>{children}</div>
  </div>
)
CollapsibleContent.displayName = 'CollapsibleContent'

export { Collapsible, CollapsibleContent, CollapsibleTrigger }

Usage

<Collapsible openByDefault={true} animation="scale-fade">
  <CollapsibleTrigger className="p-3">
    Introduction to Collapsible Component
  </CollapsibleTrigger>
  <CollapsibleContent className="p-3">
    This section provides a brief overview of the Collapsible Component, illustrating how it can be utilized to create expandable and collapsible sections within your application. Ideal for organizing content or hiding detailed information until requested by the user.
  </CollapsibleContent>
</Collapsible>