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;
};

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) {
      // Clear any existing timer
      if (timerReference.current) {
        clearTimeout(timerReference.current);
      }

      const rafId = requestAnimationFrame(() => {
        setIsLoading(true);
        setHasBeenOpened(true);
      });

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

      return () => {
        cancelAnimationFrame(rafId);
        if (timerReference.current) {
          clearTimeout(timerReference.current);
          timerReference.current = null;
        }
      };
    }

    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