index.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import { cn } from '@/lib/utils'
   3  import { useEffect, useRef, useState } from 'react'
   4  import { useTranslation } from 'react-i18next'
   5  
   6  export default function Collapsible({
   7    alwaysExpand = false,
   8    children,
   9    className,
  10    threshold = 1000,
  11    collapsedHeight = 600,
  12    ...props
  13  }: {
  14    alwaysExpand?: boolean
  15    threshold?: number
  16    collapsedHeight?: number
  17  } & React.HTMLProps<HTMLDivElement>) {
  18    const { t } = useTranslation()
  19    const containerRef = useRef<HTMLDivElement>(null)
  20    const [expanded, setExpanded] = useState(false)
  21    const [shouldCollapse, setShouldCollapse] = useState(false)
  22  
  23    useEffect(() => {
  24      if (alwaysExpand || shouldCollapse) return
  25  
  26      const contentEl = containerRef.current
  27      if (!contentEl) return
  28  
  29      const checkHeight = () => {
  30        const fullHeight = contentEl.scrollHeight
  31        if (fullHeight > threshold) {
  32          setShouldCollapse(true)
  33        }
  34      }
  35  
  36      checkHeight()
  37  
  38      const observer = new ResizeObserver(() => {
  39        checkHeight()
  40      })
  41  
  42      observer.observe(contentEl)
  43  
  44      return () => {
  45        observer.disconnect()
  46      }
  47    }, [alwaysExpand, shouldCollapse])
  48  
  49    return (
  50      <div
  51        className={cn('relative text-left overflow-hidden', className)}
  52        ref={containerRef}
  53        {...props}
  54        style={{
  55          maxHeight: !shouldCollapse || expanded ? 'none' : `${collapsedHeight}px`
  56        }}
  57      >
  58        {children}
  59        {shouldCollapse && !expanded && (
  60          <div className="absolute bottom-0 h-40 w-full z-10 bg-gradient-to-b from-transparent to-background/90 flex items-end justify-center pb-4">
  61            <div className="bg-background rounded-lg">
  62              <Button
  63                className="bg-foreground hover:bg-foreground/80"
  64                data-collapsible-expand
  65                onClick={(e) => {
  66                  e.stopPropagation()
  67                  setExpanded(!expanded)
  68                }}
  69              >
  70                {t('Show more')}
  71                <span className="ml-2 text-xs opacity-60 font-mono">⇧M</span>
  72              </Button>
  73            </div>
  74          </div>
  75        )}
  76      </div>
  77    )
  78  }
  79