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