TLDR Section Component

A collapsible section component with a smooth loading animation. Perfect for hiding lengthy content that users might want to skip.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/tldr-section.json
bash

Usage

import { TLDRSection } from '@vllnt/ui'

export function TLDRSectionExample() {
return (
  <TLDRSection label="Click to expand">
    <p>Your lengthy content here...</p>
  </TLDRSection>
)
}
tsx

Code

'use client'

import { useEffect, useRef, useState } from 'react'

type TLDRSectionProps = {
  children: React.ReactNode
  label: string
}

// eslint-disable-next-line max-lines-per-function
export function TLDRSection({ children, label }: TLDRSectionProps) {
  const [isExpanded, setIsExpanded] = useState(false)
  const [isLoading, setIsLoading] = useState(false)
  const [hasBeenOpened, setHasBeenOpened] = useState(false)
  const timerReference = useRef<NodeJS.Timeout | null>(null)

  useEffect(() => {
    if (isExpanded && !hasBeenOpened) {
      setIsLoading(true)
      setHasBeenOpened(true)

      // Clear any existing timer
      if (timerReference.current) {
        clearTimeout(timerReference.current)
      }

      // Simulate loading with skeleton
      timerReference.current = setTimeout(() => {
        setIsLoading(false)
        timerReference.current = null
      }, 800)
    }

    return () => {
      if (timerReference.current) {
        clearTimeout(timerReference.current)
        timerReference.current = null
      }
    }
  }, [isExpanded]) // eslint-disable-line react-hooks/exhaustive-deps

  return (
    <div className="my-8 rounded-lg border border-border bg-muted/30 overflow-hidden">
      <button
        className="flex items-center justify-between w-full px-4 py-3 hover:bg-muted/50 transition-colors"
        onClick={() => {
          setIsExpanded(!isExpanded)
        }}
        type="button"
      >
        <div className="flex items-center gap-3">
          <svg
            className="w-5 h-5 text-muted-foreground"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            viewBox="0 0 24 24"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path d="M4 6h16M4 12h16M4 18h16" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          <span className="text-sm font-medium text-foreground">{label}</span>
        </div>
        <svg
          className={`w-4 h-4 text-muted-foreground transition-transform ${isExpanded ? 'rotate-180' : ''}`}
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          viewBox="0 0 24 24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path d="M19 9l-7 7-7-7" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
      {isExpanded ? (
        <div className="px-4 pb-4 pt-2 border-t border-border">
          {isLoading ? (
            <div className="space-y-3">
              <div className="relative h-4 bg-muted/50 rounded overflow-hidden">
                <div className="absolute inset-0 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent animate-shimmer" />
              </div>
              <div className="relative h-4 bg-muted/50 rounded overflow-hidden w-5/6">
                <div className="absolute inset-0 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent animate-shimmer" />
              </div>
              <div className="relative h-4 bg-muted/50 rounded overflow-hidden w-4/5">
                <div className="absolute inset-0 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent animate-shimmer" />
              </div>
              <div className="relative h-4 bg-muted/50 rounded overflow-hidden w-3/4">
                <div className="absolute inset-0 bg-gradient-to-r from-transparent via-amber-400/40 to-transparent animate-shimmer" />
              </div>
            </div>
          ) : (
            <div className="text-sm text-muted-foreground leading-relaxed">{children}</div>
          )}
        </div>
      ) : null}
    </div>
  )
}
typescript