104 lines
2.7 KiB
TypeScript
104 lines
2.7 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { Send } from 'lucide-react';
|
|
import { useSuspendPlayback } from '@/hooks/use-suspend-playback';
|
|
import { sanitizeReactionText } from '@/lib/firestore-particles';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const MAX_LENGTH = 40;
|
|
|
|
interface TextReactionInputProps {
|
|
open: boolean;
|
|
onSubmit: (text: string) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function TextReactionInput({
|
|
open,
|
|
onSubmit,
|
|
onClose,
|
|
}: TextReactionInputProps) {
|
|
const [value, setValue] = useState('');
|
|
const [prevOpen, setPrevOpen] = useState(open);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
useSuspendPlayback(open, 'text-reaction');
|
|
|
|
if (open !== prevOpen) {
|
|
setPrevOpen(open);
|
|
|
|
// NOTE: perform side effects here when opening
|
|
if (open) {
|
|
setValue('')
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const id = requestAnimationFrame(() => inputRef.current?.focus());
|
|
return () => cancelAnimationFrame(id);
|
|
}, [open]);
|
|
|
|
if (!open) return null;
|
|
|
|
const trimmed = value.trim();
|
|
const canSubmit = trimmed.length > 0;
|
|
const remaining = MAX_LENGTH - value.length;
|
|
|
|
const handleSubmit = () => {
|
|
if (!canSubmit) return;
|
|
onSubmit(trimmed);
|
|
setValue('');
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<div
|
|
onClick={(e) => e.stopPropagation()}
|
|
className="flex items-center gap-1 rounded-full bg-black/60 py-1 pl-3 pr-1 shadow-lg ring-1 ring-white/15 backdrop-blur-md"
|
|
>
|
|
<input
|
|
ref={inputRef}
|
|
value={value}
|
|
onChange={(e) =>
|
|
setValue(sanitizeReactionText(e.target.value).slice(0, MAX_LENGTH))
|
|
}
|
|
onBlur={onClose}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
handleSubmit();
|
|
} else if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
onClose();
|
|
}
|
|
}}
|
|
placeholder="Quick reply…"
|
|
maxLength={MAX_LENGTH}
|
|
className="w-24 bg-transparent text-sm text-white outline-none placeholder:text-white/40"
|
|
/>
|
|
<span
|
|
className={cn(
|
|
'min-w-[1.5ch] text-right text-[10px] tabular-nums',
|
|
remaining <= 8 ? 'text-amber-300/80' : 'text-white/30',
|
|
)}
|
|
>
|
|
{remaining}
|
|
</span>
|
|
<button
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
onClick={handleSubmit}
|
|
disabled={!canSubmit}
|
|
className={cn(
|
|
'ml-1 flex size-6 items-center justify-center rounded-full transition-colors',
|
|
canSubmit
|
|
? 'bg-white/20 text-white hover:bg-white/30'
|
|
: 'text-white/30',
|
|
)}
|
|
aria-label="Send reaction"
|
|
>
|
|
<Send className="size-3" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|