import React, { useCallback, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import styles from './Controls.module.css'; type ButtonProps = { children: React.ReactNode; onClick?: () => void; variant?: 'primary' | 'ghost'; disabled?: boolean; title?: string | undefined; /** Подпись для скринридеров (иконки без текста). */ ariaLabel?: string | undefined; /** Компактная кнопка под одну иконку. */ iconOnly?: boolean; }; export function Button({ children, onClick, variant = 'ghost', disabled = false, title, ariaLabel, iconOnly = false, }: ButtonProps) { const btnRef = useRef(null); const [tipPos, setTipPos] = useState<{ x: number; y: number } | null>(null); const showTip = useCallback(() => { if (disabled || !title) return; const el = btnRef.current; if (!el) return; const r = el.getBoundingClientRect(); setTipPos({ x: r.left + r.width / 2, y: r.top }); }, [disabled, title]); const hideTip = useCallback(() => { setTipPos(null); }, []); const btnClass = [ styles.button, variant === 'primary' ? styles.buttonPrimary : '', iconOnly ? styles.iconOnly : '', ] .filter(Boolean) .join(' '); const tip = title && tipPos && typeof document !== 'undefined' ? createPortal(
{title}
, document.body, ) : null; return ( <> {tip} ); } type InputProps = { value: string; placeholder?: string; onChange: (v: string) => void; }; export function Input({ value, placeholder, onChange }: InputProps) { return ( onChange(e.target.value)} /> ); }