Reveal
- Easy integration with any ReactNode content
- Conditionally renders the button based on content height
- Customizable maximum height for flexible content display
- Smoothly handles dynamic content changes with ResizeObserver
- Offers multiple button styles for different UI needs
Installation
Add the following Soul components
The reveal component uses the animated-underline and button components. Make sure you have added them to your project.
Install the following dependencies
npm install clsx
Copy and paste the following code into your project
primitives/reveal/index.tsx
'use client';import { clsx } from 'clsx';import { ReactNode, useEffect, useRef, useState } from 'react';import { AnimatedUnderline } from '@/vibes/soul/primitives/animated-underline';import { Button } from '@/vibes/soul/primitives/button';export interface RevealProps { variant?: 'underline' | 'button'; showLabel?: string; hideLabel?: string; defaultOpen?: boolean; children: ReactNode; maxHeight?: string;}export function Reveal({ variant = 'underline', showLabel = 'Show more', hideLabel = 'Show less', defaultOpen = false, maxHeight = '10rem', children,}: RevealProps) { const [isOpen, setIsOpen] = useState(defaultOpen); const [hasOverflow, setHasOverflow] = useState(true); const contentRef = useRef<HTMLDivElement>(null); function convertToPixels(value: string): number { const num = parseFloat(value); if (value.endsWith('rem')) { return num * 16; // Convert rem to pixels (1rem = 16px) } if (value.endsWith('px')) { return num; } return num; } useEffect(() => { function checkHeight() { if (contentRef.current) { const contentHeight = contentRef.current.scrollHeight; const maxHeightPx = convertToPixels(maxHeight); setHasOverflow(contentHeight > maxHeightPx); } } checkHeight(); const resizeObserver = new ResizeObserver(checkHeight); if (contentRef.current) { resizeObserver.observe(contentRef.current); } return () => { resizeObserver.disconnect(); }; }, [maxHeight]); return ( <div className="relative"> <div ref={contentRef} className={clsx( hasOverflow && !isOpen && '[mask-image:linear-gradient(to_top,transparent,black_50px,black_calc(100%-50px))]', 'overflow-hidden', )} style={{ maxHeight: isOpen ? 'none' : maxHeight }} > {children} </div> {hasOverflow && ( <div className={clsx('flex w-full items-end pt-4')}> {variant === 'underline' && ( <button className="group/underline text-sm focus:outline-none" onClick={() => setIsOpen(!isOpen)} type="button" > <AnimatedUnderline>{isOpen ? hideLabel : showLabel}</AnimatedUnderline> </button> )} {variant === 'button' && ( <Button variant="tertiary" size="x-small" onClick={() => setIsOpen(!isOpen)} type="button" > {isOpen ? hideLabel : showLabel} </Button> )} </div> )} </div> );}
Usage
import { Reveal } from '@/vibes/soul/primitives/reveal';export default function Preview() {return ( <Reveal> <p> To begin your return, simply log into your account on our website or contact our dedicated customer service team. Once your return is authorized, we'll provide you with a prepaid shipping label for domestic returns. </p> </Reveal>);}
API Reference
RevealProps
Prop | Type | Default |
---|---|---|
children* | ReactNode | |
defaultOpen | boolean | false |
hideLabel | string | 'Show less' |
maxHeight | string | '10rem' |
showLabel | string | 'Show more' |
variant | 'underline' | 'button' | 'underline' |