Expandable Panel

An interactive expandable panel component that displays images in collapsible sections. Click on any panel to expand it while others collapse smoothly. Now supports clicking outside to collapse all panels.

React
Layout
Interactive
Animation
Gallery
Architecture 1
Architecture 2
Architecture 3
Architecture 4
Architecture 5
Architecture 6
Architecture 7

Using CLI

npx dimaac add ExpandablePanel

Manual Installation

npm install react

lib/utils.ts

import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
} 

components/ui/ExpandablePanel.tsx

'use client';

import React, { useState, useEffect, useRef } from 'react';
import { cn } from '@/lib/utils';

interface Panel {
  image: string;
  alt?: string;
}

interface ExpandablePanelProps {
  panels: Panel[];
  className?: string;
  panelClassName?: string;
  expandedWidth?: string;
  collapsedWidth?: string;
  minWidth?: string;
  height?: string;
  gap?: string;
  borderRadius?: string;
  transitionDuration?: string;
  defaultExpanded?: number;
}

const ExpandablePanel: React.FC<ExpandablePanelProps> = ({
  panels,
  className,
  panelClassName,
  expandedWidth = '60%',
  collapsedWidth = '10%',
  minWidth = '40px',
  height = '80vh',
  gap = '0.5rem',
  borderRadius = '1rem',
  transitionDuration = '500ms',
  defaultExpanded = 0
}) => {
  const [expandedIndex, setExpandedIndex] = useState(defaultExpanded);
  const panelRef = useRef<HTMLDivElement>(null);

  const handleClick = (index: number) => {
    setExpandedIndex(index);
  };

  // Handle clicks outside the panel
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
        setExpandedIndex(-1);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, []);

  return (
    <div 
      ref={panelRef}
      className={cn(
        "flex w-full max-w-7xl items-center justify-center",
        className
      )}
      style={{
        height,
        gap
      }}
    >
      {panels.map((panel, index) => (
        <div
          key={index}
          onClick={() => handleClick(index)}
          className={cn(
            "h-full cursor-pointer transition-all ease-in-out overflow-hidden block",
            panelClassName
          )}
          style={{
            width: expandedIndex === index ? expandedWidth : collapsedWidth,
            minWidth,
            borderRadius,
            transitionDuration
          }}
        >
          <img 
            src={panel.image} 
            alt={panel.alt || `Panel ${index + 1}`}
            className="w-full h-full object-cover object-top"
          />
        </div>
      ))}
    </div>
  );
};

export default ExpandablePanel;