Skip to main content
Glama
index.tsx2.94 kB
'use client'; import { Eye, EyeOff } from 'lucide-react'; import { type FC, useEffect, useMemo, useRef, useState } from 'react'; import { cn } from '../../utils/cn'; /** * Props for the HideShow component */ export interface HideShowProps { /** * Sensitive text to display masked. Copy is only allowed when revealed. */ text: string; /** Wrapper classes */ className?: string; /** Number of prefix characters to keep visible when masked. Default: 6 */ visiblePrefixChars?: number; /** Character used to mask hidden portion. Default: '•' */ maskChar?: string; /** Reveal duration in ms before auto-hiding. Default: 10000 (10s) */ revealDurationMs?: number; /** Copy error callback */ onCopyError?: (error: Error) => void; } // Insert zero-width spaces every N chars so Safari can wrap long runs const insertBreaks = (str: string, groupSize = 6) => str.replace(new RegExp(`.{1,${groupSize}}`, 'g'), '$&\u200b'); export const HideShow: FC<HideShowProps> = ({ text, className, visiblePrefixChars = 6, maskChar = '•', revealDurationMs = 10000, }) => { const [isRevealed, setIsRevealed] = useState(false); const hideTimerRef = useRef<number | null>(null); const maskedText = useMemo(() => { if (!text) return ''; if (visiblePrefixChars <= 0) return maskChar.repeat(text.length); const prefix = text.slice(0, visiblePrefixChars); const remaining = Math.max(0, text.length - visiblePrefixChars); return insertBreaks(`${prefix}${maskChar.repeat(remaining)}`); }, [text, visiblePrefixChars, maskChar]); useEffect(() => { return () => { if (hideTimerRef.current) { window.clearTimeout(hideTimerRef.current); hideTimerRef.current = null; } }; }, []); const reveal = () => { if (isRevealed) return; setIsRevealed(true); if (hideTimerRef.current) { window.clearTimeout(hideTimerRef.current); hideTimerRef.current = null; } hideTimerRef.current = window.setTimeout(() => { setIsRevealed(false); }, revealDurationMs); }; const hide = () => { setIsRevealed(false); if (hideTimerRef.current) { window.clearTimeout(hideTimerRef.current); hideTimerRef.current = null; } }; const IconComponent = isRevealed ? EyeOff : Eye; return ( <span className={cn( 'inline-flex max-w-full items-center gap-2 rounded-md p-0.5 hover:cursor-pointer hover:bg-neutral/10', className )} onClick={isRevealed ? hide : reveal} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); isRevealed ? hide() : reveal(); } }} role="button" tabIndex={0} > <span className="min-w-0 break-all"> {isRevealed ? text : maskedText} </span> <IconComponent className="ml-1 ml-auto size-4 min-w-4 shrink-0" /> </span> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server