Skip to main content
Glama
ContentSelector.tsx3.59 kB
'use client'; import type { FC, HTMLAttributes, MouseEventHandler } from 'preact/compat'; import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; const DEFAULT_PRESS_DETECT_DURATION = 250; type ContentSelectorProps = { onPress: () => void; onHover?: () => void; onUnhover?: () => void; onClickOutside?: () => void; pressDuration?: number; isSelecting?: boolean; } & Omit<HTMLAttributes<HTMLDivElement>, 'content'>; export const ContentSelector: FC<ContentSelectorProps> = ({ children, onPress: onSelect, onHover, onUnhover, onClickOutside: onUnselect, pressDuration = DEFAULT_PRESS_DETECT_DURATION, isSelecting: isSelectingProp, ...props }) => { const divRef = useRef<HTMLDivElement>(null); const [isHovered, setIsHovered] = useState(false); const [isSelectingState, setIsSelectingState] = useState(isSelectingProp); const pressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const isChildrenString = typeof children === 'string'; const handleOnLongPress = () => { setIsSelectingState(true); onSelect(); }; const startPressTimer = () => { pressTimerRef.current = setTimeout(() => { handleOnLongPress(); }, pressDuration); }; const clearPressTimer = () => { if (pressTimerRef.current) { clearTimeout(pressTimerRef.current); pressTimerRef.current = null; } }; const handleMouseDown = () => { clearPressTimer(); // Ensure any previous timer is cleared startPressTimer(); }; const handleMouseEnter = () => { setIsHovered(true); onHover?.(); }; const handleMouseUp = () => { if (isHovered) { setIsHovered(false); onUnhover?.(); } clearPressTimer(); }; // Use useCallback to ensure the function identity remains stable const handleClickOutside = useCallback( (event: MouseEvent) => { if (divRef.current && !divRef.current.contains(event.target as Node)) { setIsSelectingState(false); onUnselect?.(); } }, [onUnselect] ); useEffect(() => { // Attach click outside listener document.addEventListener('mousedown', handleClickOutside); return () => { // Cleanup document.removeEventListener('mousedown', handleClickOutside); // clearPressTimer(); // Ensure to clear the timer when component unmounts }; }, [handleClickOutside]); const handleOnClick: MouseEventHandler<HTMLDivElement> = (e) => { if (isSelectingState) { e.preventDefault(); e.stopPropagation(); } }; const handleOnBlur = () => { // Stop editing when the element loses focus setIsSelectingState(false); }; return ( <span style={{ display: isChildrenString ? 'inline' : 'inline-block', cursor: 'pointer', userSelect: 'none', borderRadius: '0.375rem', outlineWidth: '2px', outlineOffset: '4px', outlineStyle: 'solid', outlineColor: isSelectingProp || isSelectingState || isHovered ? 'inherit' : 'transparent', transition: 'all 100ms 50ms ease-in-out', }} role="button" tabIndex={0} onKeyUp={() => null} onClick={handleOnClick} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp} onTouchStart={handleMouseDown} onTouchEnd={handleMouseUp} onTouchCancel={handleMouseUp} onBlur={handleOnBlur} onMouseEnter={handleMouseEnter} ref={divRef} {...props} > {children} </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